(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (factory((global.Formula = global.Formula || {})));
}(this, function (exports) { 'use strict';

    // Copyright 2015 JC Fisher

    // List of errors in the spreadsheet system

    function FormulaError(name, message) {
      this.name = name || "NotImplementedError";
      this.message = message || "";
    }

    FormulaError.prototype = Error.prototype;
    FormulaError.prototype.toString = function() {
      return this.name;
    };

    var nil = new FormulaError("#NULL!", "Null reference");
    var div0 = new FormulaError("#DIV/0!", "Divide by zero");
    var value = new FormulaError("#VALUE!", "Invalid value");
    var ref = new FormulaError("#REF!", "Invalid reference");
    var name = new FormulaError("#NAME?", "Invalid name");
    var num = new FormulaError("#NUM!", "Invalid number");
    var na = new FormulaError("#N/A!", "Not applicable");
    var error$1 = new FormulaError("#ERROR!", "Error");
    var data = new FormulaError("#GETTING_DATA!", "Error getting data");
    var missing = new FormulaError("#MISSING!", "Missing");
    var unknown = new FormulaError("#UNKNOWN!", "Unknown error");
    var error$2 = {
      nil: nil,
      "#NULL!": nil,
      div0: div0,
      "#DIV/0!": div0,
      value: value,
      "#VALUE!": value,
      ref: ref,
      "#REF!": ref,
      name: name,
      "#NAME?": name,
      num: num,
      "#NUM!": num,
      na: na,
      "#N/A!": na,
      error: error$1,
      "#ERROR!": error$1,
      data: data,
      "#GETTING_DATA!": data,
      missing: missing,
      "#MISSING!": missing,
      unknown: unknown,
      "#UNKNOWN!": unknown
    };function _error(type) {
      return error$2[type] || error$1;
    }

    /* parser generated by jison 0.4.18 */
    /*
      Returns a Parser object of the following structure:

      Parser: {
        yy: {}
      }

      Parser.prototype: {
        yy: {},
        trace: function(),
        symbols_: {associative list: name ==> number},
        terminals_: {associative list: number ==> name},
        productions_: [...],
        performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
        table: [...],
        defaultActions: {...},
        parseError: function(str, hash),
        parse: function(input),

        lexer: {
            EOF: 1,
            parseError: function(str, hash),
            setInput: function(input),
            input: function(),
            unput: function(str),
            more: function(),
            less: function(n),
            pastInput: function(),
            upcomingInput: function(),
            showPosition: function(),
            test_match: function(regex_match_array, rule_index),
            next: function(),
            lex: function(),
            begin: function(condition),
            popState: function(),
            _currentRules: function(),
            topState: function(),
            pushState: function(condition),

            options: {
                ranges: boolean           (optional: true ==> token location info will include a .range[] member)
                flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
                backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
            },

            performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
            rules: [...],
            conditions: {associative list: name ==> set},
        }
      }


      token location info (@$, _$, etc.): {
        first_line: n,
        last_line: n,
        first_column: n,
        last_column: n,
        range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
      }


      the parseError function receives a 'hash' object with these members for lexer and parser errors: {
        text:        (matched text)
        token:       (the produced terminal token, if any)
        line:        (yylineno)
      }
      while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
        loc:         (yylloc)
        expected:    (string describing the set of expected tokens)
        recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
      }
    */
    var parser = (function(){
    var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v){ ; }return o},$V0=[1,4],$V1=[1,5],$V2=[1,6],$V3=[1,7],$V4=[1,8],$V5=[1,11],$V6=[1,12],$V7=[1,13],$V8=[1,14],$V9=[1,15],$Va=[1,16],$Vb=[1,24],$Vc=[1,18],$Vd=[1,19],$Ve=[1,20],$Vf=[1,21],$Vg=[1,22],$Vh=[1,23],$Vi=[1,25],$Vj=[1,26],$Vk=[1,27],$Vl=[1,28],$Vm=[1,29],$Vn=[1,30],$Vo=[5,6,7,8,9,10,11,12,13,14,15,16,17,19,20,32,33,36],$Vp=[5,6,7,8,12,13,14,15,16,17,19,32,33,36],$Vq=[1,58],$Vr=[1,59],$Vs=[19,32,33,36],$Vt=[5,6,7,8,9,10,12,13,14,15,16,17,19,32,33,36],$Vu=[5,6,12,13,14,15,16,19,32,33,36];
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"=":6,"+":7,"-":8,"*":9,"/":10,"^":11,"<>":12,">":13,"<":14,">=":15,"<=":16,"&":17,"(":18,")":19,":":20,"IDENT":21,"SCOPE":22,"func":23,"array_literal":24,"TRUE":25,"FALSE":26,"STRING":27,"NUMBER":28,"%":29,"range":30,"param_list":31,",":32,";":33,"FUNC":34,"{":35,"}":36,"$accept":0,"$end":1},
    terminals_: {2:"error",5:"EOF",6:"=",7:"+",8:"-",9:"*",10:"/",11:"^",12:"<>",13:">",14:"<",15:">=",16:"<=",17:"&",18:"(",19:")",20:":",21:"IDENT",22:"SCOPE",25:"TRUE",26:"FALSE",27:"STRING",28:"NUMBER",29:"%",32:",",33:";",34:"FUNC",35:"{",36:"}"},
    productions_: [0,[3,2],[3,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,2],[4,3],[4,2],[4,3],[4,3],[4,1],[4,2],[4,1],[4,1],[4,1],[4,1],[4,1],[4,2],[4,1],[30,3],[31,1],[31,3],[31,3],[23,4],[23,3],[24,3]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
    /* this == yyval */

    var $0 = $$.length - 1;
    switch (yystate) {
    case 1: case 2:
     return $$[$0-1];
    break;
    case 3:
    this.$ = { type: "operator", subtype: 'infix-add', operands:[$$[$0-2], $$[$0]]};
    break;
    case 4:
    this.$ = { type: "operator", subtype: 'infix-subtract', operands:[$$[$0-2], $$[$0]]};
    break;
    case 5:
    this.$ = { type: "operator", subtype: 'infix-multiply', operands:[$$[$0-2], $$[$0]]};
    break;
    case 6:
    this.$ = { type: "operator", subtype: 'infix-divide', operands:[$$[$0-2], $$[$0]] };
    break;
    case 7:
    this.$ = { type: "operator", subtype: 'infix-power', operands:[$$[$0-2], $$[$0]] };
    break;
    case 8:
    this.$ = { type: "operator", subtype: 'infix-ne', operands:[$$[$0-2], $$[$0]] };
    break;
    case 9:
    this.$ = { type: "operator", subtype: 'infix-eq', operands:[$$[$0-2], $$[$0]] };
    break;
    case 10:
    this.$ = { type: "operator", subtype: 'infix-gt', operands:[$$[$0-2], $$[$0]] };
    break;
    case 11:
    this.$ = { type: "operator", subtype: 'infix-lt', operands:[$$[$0-2], $$[$0]] };
    break;
    case 12:
    this.$ = { type: "operator", subtype: 'infix-gte', operands:[$$[$0-2], $$[$0]] };
    break;
    case 13:
    this.$ = { type: "operator", subtype: 'infix-lte', operands:[$$[$0-2], $$[$0]] };
    break;
    case 14:
    this.$ = { type: "operator", subtype: 'prefix-plus', operands:[$$[$0]] };
    break;
    case 15:
    this.$ = { type: "operator", subtype: 'infix-concat', operands:[$$[$0-2], $$[$0]] };
    break;
    case 16:
    this.$ = { type: "operator", subtype: 'prefix-minus', operands:[$$[$0]] };
    break;
    case 17:
    this.$ = { type: 'group', exp:$$[$0-1] };
    break;
    case 18:
    this.$ = { type: 'range', subtype: 'local', topLeft:$$[$0-2], bottomRight:$$[$0] };
    break;
    case 19:
     this.$ = { type: 'variable', name:$$[$0] };
    break;
    case 20:
     this.$ = { type: 'variable', scope: $$[$0-1], name: $$[$0] };
    break;
    case 21: case 22:
     this.$ = $$[$0];
    break;
    case 23:
    this.$ = { type: 'value', subtype: 'boolean', value: true };
    break;
    case 24:
    this.$ = { type: 'value', subtype: 'boolean', value: false };
    break;
    case 25:
    this.$ = { type: 'value', subtype: 'string', value:String(yytext)};
    break;
    case 26:
    this.$ = { type: 'value', subtype: 'number', value:$$[$0-1]/100 };
    break;
    case 27:
    this.$ = { type: 'value', subtype: 'number', value:Number(yytext) };
    break;
    case 29:
     this.$ = [$$[$0]];
    break;
    case 30:
     this.$ = $$[$0-2].concat([$$[$0]]);
    break;
    case 31:
     this.$ = ($$[$0][0].subtype !== 'array') ? [{ type: 'value', subtype: 'array', items:$$[$0-2] }, { type: 'value', subtype: 'array', items:$$[$0] }] : [{ type: 'value', subtype: 'array', items:$$[$0-2] }].concat($$[$0]);
    break;
    case 32:
     this.$ = { type: 'function', name: $$[$0-3], args:$$[$0-1] };
    break;
    case 33:
     this.$ = { type: 'function', name: $$[$0-2], args:[] };
    break;
    case 34:
     this.$ = { type: 'value', subtype: 'array', items:$$[$0-1] };
    break;
    }
    },
    table: [{3:1,4:2,6:[1,3],7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{1:[3]},{5:[1,17],6:$Vb,7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,12:$Vh,13:$Vi,14:$Vj,15:$Vk,16:$Vl,17:$Vm,20:$Vn},{4:31,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:32,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:33,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:34,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},o($Vo,[2,19]),{21:[1,35]},o($Vo,[2,21]),o($Vo,[2,22]),o($Vo,[2,23]),o($Vo,[2,24]),o($Vo,[2,25]),o($Vo,[2,27],{29:[1,36]}),{18:[1,37]},{4:39,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,31:38,34:$V9,35:$Va},{1:[2,1]},{4:40,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:41,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:42,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:43,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:44,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:45,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:46,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:47,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:48,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:49,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:50,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:51,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:52,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{5:[1,53],6:$Vb,7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,12:$Vh,13:$Vi,14:$Vj,15:$Vk,16:$Vl,17:$Vm,20:$Vn},o($Vp,[2,14],{9:$Ve,10:$Vf,11:$Vg,20:$Vn}),o($Vp,[2,16],{9:$Ve,10:$Vf,11:$Vg,20:$Vn}),{6:$Vb,7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,12:$Vh,13:$Vi,14:$Vj,15:$Vk,16:$Vl,17:$Vm,19:[1,54],20:$Vn},o($Vo,[2,20]),o($Vo,[2,26]),{4:39,7:$V0,8:$V1,18:$V2,19:[1,56],21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,31:55,34:$V9,35:$Va},{32:$Vq,33:$Vr,36:[1,57]},o($Vs,[2,29],{6:$Vb,7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,12:$Vh,13:$Vi,14:$Vj,15:$Vk,16:$Vl,17:$Vm,20:$Vn}),o($Vp,[2,3],{9:$Ve,10:$Vf,11:$Vg,20:$Vn}),o($Vp,[2,4],{9:$Ve,10:$Vf,11:$Vg,20:$Vn}),o($Vt,[2,5],{11:$Vg,20:$Vn}),o($Vt,[2,6],{11:$Vg,20:$Vn}),o([5,6,7,8,9,10,11,12,13,14,15,16,17,19,32,33,36],[2,7],{20:$Vn}),o($Vu,[2,8],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o($Vu,[2,9],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o($Vu,[2,10],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o($Vu,[2,11],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o($Vu,[2,12],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o($Vu,[2,13],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,17:$Vm,20:$Vn}),o([5,6,12,13,14,15,16,17,19,32,33,36],[2,15],{7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,20:$Vn}),o($Vo,[2,18]),{1:[2,2]},o($Vo,[2,17]),{19:[1,60],32:$Vq,33:$Vr},o($Vo,[2,33]),o($Vo,[2,34]),{4:61,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,34:$V9,35:$Va},{4:39,7:$V0,8:$V1,18:$V2,21:$V3,22:$V4,23:9,24:10,25:$V5,26:$V6,27:$V7,28:$V8,31:62,34:$V9,35:$Va},o($Vo,[2,32]),o($Vs,[2,30],{6:$Vb,7:$Vc,8:$Vd,9:$Ve,10:$Vf,11:$Vg,12:$Vh,13:$Vi,14:$Vj,15:$Vk,16:$Vl,17:$Vm,20:$Vn}),o([19,36],[2,31],{32:$Vq,33:$Vr})],
    defaultActions: {17:[2,1],53:[2,2]},
    parseError: function parseError(str, hash) {
        if (hash.recoverable) {
            this.trace(str);
        } else {
            var error = new Error(str);
            error.hash = hash;
            throw error;
        }
    },
    parse: function parse(input) {
        var this$1 = this;

        var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
        var args = lstack.slice.call(arguments, 1);
        var lexer = Object.create(this.lexer);
        var sharedState = { yy: {} };
        for (var k in this$1.yy) {
            if (Object.prototype.hasOwnProperty.call(this$1.yy, k)) {
                sharedState.yy[k] = this$1.yy[k];
            }
        }
        lexer.setInput(input, sharedState.yy);
        sharedState.yy.lexer = lexer;
        sharedState.yy.parser = this;
        if (typeof lexer.yylloc == 'undefined') {
            lexer.yylloc = {};
        }
        var yyloc = lexer.yylloc;
        lstack.push(yyloc);
        var ranges = lexer.options && lexer.options.ranges;
        if (typeof sharedState.yy.parseError === 'function') {
            this.parseError = sharedState.yy.parseError;
        } else {
            this.parseError = Object.getPrototypeOf(this).parseError;
        }
        function popStack(n) {
            stack.length = stack.length - 2 * n;
            vstack.length = vstack.length - n;
            lstack.length = lstack.length - n;
        }
        var lex = function () {
            var token;
            token = lexer.lex() || EOF;
            if (typeof token !== 'number') {
                token = self.symbols_[token] || token;
            }
            return token;
        };
        var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
        while (true) {
            state = stack[stack.length - 1];
            if (this$1.defaultActions[state]) {
                action = this$1.defaultActions[state];
            } else {
                if (symbol === null || typeof symbol == 'undefined') {
                    symbol = lex();
                }
                action = table[state] && table[state][symbol];
            }
                        if (typeof action === 'undefined' || !action.length || !action[0]) {
                    var errStr = '';
                    expected = [];
                    for (p in table[state]) {
                        if (this$1.terminals_[p] && p > TERROR) {
                            expected.push('\'' + this$1.terminals_[p] + '\'');
                        }
                    }
                    if (lexer.showPosition) {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this$1.terminals_[symbol] || symbol) + '\'';
                    } else {
                        errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this$1.terminals_[symbol] || symbol) + '\'');
                    }
                    this$1.parseError(errStr, {
                        text: lexer.match,
                        token: this$1.terminals_[symbol] || symbol,
                        line: lexer.yylineno,
                        loc: yyloc,
                        expected: expected
                    });
                }
            if (action[0] instanceof Array && action.length > 1) {
                throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
            }
            switch (action[0]) {
            case 1:
                stack.push(symbol);
                vstack.push(lexer.yytext);
                lstack.push(lexer.yylloc);
                stack.push(action[1]);
                symbol = null;
                if (!preErrorSymbol) {
                    yyleng = lexer.yyleng;
                    yytext = lexer.yytext;
                    yylineno = lexer.yylineno;
                    yyloc = lexer.yylloc;
                    if (recovering > 0) {
                        recovering--;
                    }
                } else {
                    symbol = preErrorSymbol;
                    preErrorSymbol = null;
                }
                break;
            case 2:
                len = this$1.productions_[action[1]][1];
                yyval.$ = vstack[vstack.length - len];
                yyval._$ = {
                    first_line: lstack[lstack.length - (len || 1)].first_line,
                    last_line: lstack[lstack.length - 1].last_line,
                    first_column: lstack[lstack.length - (len || 1)].first_column,
                    last_column: lstack[lstack.length - 1].last_column
                };
                if (ranges) {
                    yyval._$.range = [
                        lstack[lstack.length - (len || 1)].range[0],
                        lstack[lstack.length - 1].range[1]
                    ];
                }
                r = this$1.performAction.apply(yyval, [
                    yytext,
                    yyleng,
                    yylineno,
                    sharedState.yy,
                    action[1],
                    vstack,
                    lstack
                ].concat(args));
                if (typeof r !== 'undefined') {
                    return r;
                }
                if (len) {
                    stack = stack.slice(0, -1 * len * 2);
                    vstack = vstack.slice(0, -1 * len);
                    lstack = lstack.slice(0, -1 * len);
                }
                stack.push(this$1.productions_[action[1]][0]);
                vstack.push(yyval.$);
                lstack.push(yyval._$);
                newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
                stack.push(newState);
                break;
            case 3:
                return true;
            }
        }
        return true;
    }};
    /* generated by jison-lex 0.3.4 */
    var lexer = (function(){
    var lexer = ({

    EOF:1,

    parseError:function parseError(str, hash) {
            if (this.yy.parser) {
                this.yy.parser.parseError(str, hash);
            } else {
                throw new Error(str);
            }
        },

    // resets the lexer, sets new input
    setInput:function (input, yy) {
            this.yy = yy || this.yy || {};
            this._input = input;
            this._more = this._backtrack = this.done = false;
            this.yylineno = this.yyleng = 0;
            this.yytext = this.matched = this.match = '';
            this.conditionStack = ['INITIAL'];
            this.yylloc = {
                first_line: 1,
                first_column: 0,
                last_line: 1,
                last_column: 0
            };
            if (this.options.ranges) {
                this.yylloc.range = [0,0];
            }
            this.offset = 0;
            return this;
        },

    // consumes and returns one char from the input
    input:function () {
            var ch = this._input[0];
            this.yytext += ch;
            this.yyleng++;
            this.offset++;
            this.match += ch;
            this.matched += ch;
            var lines = ch.match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno++;
                this.yylloc.last_line++;
            } else {
                this.yylloc.last_column++;
            }
            if (this.options.ranges) {
                this.yylloc.range[1]++;
            }

            this._input = this._input.slice(1);
            return ch;
        },

    // unshifts one char (or a string) into the input
    unput:function (ch) {
            var len = ch.length;
            var lines = ch.split(/(?:\r\n?|\n)/g);

            this._input = ch + this._input;
            this.yytext = this.yytext.substr(0, this.yytext.length - len);
            //this.yyleng -= len;
            this.offset -= len;
            var oldLines = this.match.split(/(?:\r\n?|\n)/g);
            this.match = this.match.substr(0, this.match.length - 1);
            this.matched = this.matched.substr(0, this.matched.length - 1);

            if (lines.length - 1) {
                this.yylineno -= lines.length - 1;
            }
            var r = this.yylloc.range;

            this.yylloc = {
                first_line: this.yylloc.first_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.first_column,
                last_column: lines ?
                    (lines.length === oldLines.length ? this.yylloc.first_column : 0)
                     + oldLines[oldLines.length - lines.length].length - lines[0].length :
                  this.yylloc.first_column - len
            };

            if (this.options.ranges) {
                this.yylloc.range = [r[0], r[0] + this.yyleng - len];
            }
            this.yyleng = this.yytext.length;
            return this;
        },

    // When called from action, caches matched text and appends it on next action
    more:function () {
            this._more = true;
            return this;
        },

    // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
    reject:function () {
            if (this.options.backtrack_lexer) {
                this._backtrack = true;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });

            }
            return this;
        },

    // retain first n characters of the match
    less:function (n) {
            this.unput(this.match.slice(n));
        },

    // displays already matched input, i.e. for error messages
    pastInput:function () {
            var past = this.matched.substr(0, this.matched.length - this.match.length);
            return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
        },

    // displays upcoming input, i.e. for error messages
    upcomingInput:function () {
            var next = this.match;
            if (next.length < 20) {
                next += this._input.substr(0, 20-next.length);
            }
            return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
        },

    // displays the character position where the lexing error occurred, i.e. for error messages
    showPosition:function () {
            var pre = this.pastInput();
            var c = new Array(pre.length + 1).join("-");
            return pre + this.upcomingInput() + "\n" + c + "^";
        },

    // test the lexed token: return FALSE when not a match, otherwise return token
    test_match:function (match, indexed_rule) {
            var this$1 = this;

            var token,
                lines,
                backup;

            if (this.options.backtrack_lexer) {
                // save context
                backup = {
                    yylineno: this.yylineno,
                    yylloc: {
                        first_line: this.yylloc.first_line,
                        last_line: this.last_line,
                        first_column: this.yylloc.first_column,
                        last_column: this.yylloc.last_column
                    },
                    yytext: this.yytext,
                    match: this.match,
                    matches: this.matches,
                    matched: this.matched,
                    yyleng: this.yyleng,
                    offset: this.offset,
                    _more: this._more,
                    _input: this._input,
                    yy: this.yy,
                    conditionStack: this.conditionStack.slice(0),
                    done: this.done
                };
                if (this.options.ranges) {
                    backup.yylloc.range = this.yylloc.range.slice(0);
                }
            }

            lines = match[0].match(/(?:\r\n?|\n).*/g);
            if (lines) {
                this.yylineno += lines.length;
            }
            this.yylloc = {
                first_line: this.yylloc.last_line,
                last_line: this.yylineno + 1,
                first_column: this.yylloc.last_column,
                last_column: lines ?
                             lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
                             this.yylloc.last_column + match[0].length
            };
            this.yytext += match[0];
            this.match += match[0];
            this.matches = match;
            this.yyleng = this.yytext.length;
            if (this.options.ranges) {
                this.yylloc.range = [this.offset, this.offset += this.yyleng];
            }
            this._more = false;
            this._backtrack = false;
            this._input = this._input.slice(match[0].length);
            this.matched += match[0];
            token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
            if (this.done && this._input) {
                this.done = false;
            }
            if (token) {
                return token;
            } else if (this._backtrack) {
                // recover context
                for (var k in backup) {
                    this$1[k] = backup[k];
                }
                return false; // rule action called reject() implying the next rule should be tested instead.
            }
            return false;
        },

    // return next match in input
    next:function () {
            var this$1 = this;

            if (this.done) {
                return this.EOF;
            }
            if (!this._input) {
                this.done = true;
            }

            var token,
                match,
                tempMatch,
                index;
            if (!this._more) {
                this.yytext = '';
                this.match = '';
            }
            var rules = this._currentRules();
            for (var i = 0; i < rules.length; i++) {
                tempMatch = this$1._input.match(this$1.rules[rules[i]]);
                if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
                    match = tempMatch;
                    index = i;
                    if (this$1.options.backtrack_lexer) {
                        token = this$1.test_match(tempMatch, rules[i]);
                        if (token !== false) {
                            return token;
                        } else if (this$1._backtrack) {
                            match = false;
                            continue; // rule action called reject() implying a rule MISmatch.
                        } else {
                            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                            return false;
                        }
                    } else if (!this$1.options.flex) {
                        break;
                    }
                }
            }
            if (match) {
                token = this.test_match(match, rules[index]);
                if (token !== false) {
                    return token;
                }
                // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                return false;
            }
            if (this._input === "") {
                return this.EOF;
            } else {
                return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
                    text: "",
                    token: null,
                    line: this.yylineno
                });
            }
        },

    // return next match that has a token
    lex:function lex() {
            var r = this.next();
            if (r) {
                return r;
            } else {
                return this.lex();
            }
        },

    // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
    begin:function begin(condition) {
            this.conditionStack.push(condition);
        },

    // pop the previously active lexer condition state off the condition stack
    popState:function popState() {
            var n = this.conditionStack.length - 1;
            if (n > 0) {
                return this.conditionStack.pop();
            } else {
                return this.conditionStack[0];
            }
        },

    // produce the lexer rule set which is active for the currently active lexer condition state
    _currentRules:function _currentRules() {
            if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
                return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
            } else {
                return this.conditions["INITIAL"].rules;
            }
        },

    // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
    topState:function topState(n) {
            n = this.conditionStack.length - 1 - Math.abs(n || 0);
            if (n >= 0) {
                return this.conditionStack[n];
            } else {
                return "INITIAL";
            }
        },

    // alias for begin(condition)
    pushState:function pushState(condition) {
            this.begin(condition);
        },

    // return the number of states currently on the stack
    stateStackSize:function stateStackSize() {
            return this.conditionStack.length;
        },
    options: {},
    performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
    var YYSTATE=YY_START;
    switch($avoiding_name_collisions) {
    case 0:/* skip whitespace */
    break;
    case 1:return 28
    break;
    case 2:return 25
    break;
    case 3:return 26
    break;
    case 4:return 25
    break;
    case 5:return 26
    break;
    case 6:return 25
    break;
    case 7:return 26
    break;
    case 8:return 9
    break;
    case 9:return 10
    break;
    case 10:return 8
    break;
    case 11:return 7
    break;
    case 12:return 17
    break;
    case 13:return 11
    break;
    case 14:return 18
    break;
    case 15:return 19
    break;
    case 16:return ">="
    break;
    case 17:return "<="
    break;
    case 18:return "<>"
    break;
    case 19:return "="
    break;
    case 20:return ">"
    break;
    case 21:return "<"
    break;
    case 22:return "{"
    break;
    case 23:return "}"
    break;
    case 24:return "!"
    break;
    case 25:return ","
    break;
    case 26:return ":"
    break;
    case 27:return ";"
    break;
    case 28:return "%"
    break;
    case 29:return 34;
    break;
    case 30:return 34;
    break;
    case 31:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\"\"/g, "\""); return "STRING";
    break;
    case 32:yy_.yytext = yy_.yytext.substr(2,yy_.yyleng-3).replace(/\"\"/g, "\""); return "SCOPE";
    break;
    case 33:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-3).replace(/\"\"/g, "\""); return "SCOPE";
    break;
    case 34:yy_.yytext = yy_.yytext.slice(0, -1); return "SCOPE"
    break;
    case 35:yy_.yytext = yy_.yytext.slice(1, -1); return "SCOPE"
    break;
    case 36:return 21
    break;
    case 37:return 5
    break;
    case 38:return 'INVALID'
    break;
    }
    },
    rules: [/^(?:\s+)/,/^(?:[0-9]+(\.[0-9]+)?\b)/,/^(?:TRUE\b)/,/^(?:FALSE\b)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:True\b)/,/^(?:False\b)/,/^(?:\*)/,/^(?:\/)/,/^(?:-)/,/^(?:\+)/,/^(?:&)/,/^(?:\^)/,/^(?:\()/,/^(?:\))/,/^(?:>=)/,/^(?:<=)/,/^(?:<>)/,/^(?:=)/,/^(?:>)/,/^(?:<)/,/^(?:\{)/,/^(?:\})/,/^(?:!)/,/^(?:,)/,/^(?::)/,/^(?:;)/,/^(?:%)/,/^(?:[A-Za-z](?=[(]))/,/^(?:[A-Za-z][A-Za-z0-9\.]+(?=[(]))/,/^(?:"(?:""|[^"])*")/,/^(?:\$'(?:''|[^'])*'!)/,/^(?:'(?:''|[^'])*'!)/,/^(?:[a-zA-Z]([a-zA-Z0-9_.$]+)?!)/,/^(?:\$([a-zA-Z])([a-zA-Z0-9_.$]+)?!)/,/^(?:([\[\]a-zA-Z0-9_.$^@\(]+))/,/^(?:$)/,/^(?:.)/],
    conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"inclusive":true}}
    });
    return lexer;
    })();
    parser.lexer = lexer;
    function Parser () {
      this.yy = {};
    }
    Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();

    function PARSE () { return parser.parse.apply(parser, arguments); };

    // Copyright 2015 JC Fisher

    // reduce an array to a value
    function reduce(arr, func) {
      var rest = [], len = arguments.length - 2;
      while ( len-- > 0 ) rest[ len ] = arguments[ len + 2 ];

      return arr.reduce.apply(arr, [ func ].concat( rest ))
    }

    // ISERROR returns true when any of the values is an error.
    function iserror() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];

      return reduce(
        values,
        function (p, v) { return (p === true ? true : v instanceof Error); },
        false
      );
    }

    // Copyright 2015 JC Fisher

    // ISFUNCTION returns true when `value` is a function.
    function isfunction(value) {
        return value && Object.prototype.toString.call(value) == '[object Function]';
    };

    // AND reduces list of truthy values into true or false value.
    function and() {
      var criteria = [], len = arguments.length;
      while ( len-- ) criteria[ len ] = arguments[ len ];


      // Reduce criteria into boolean value.
      return reduce(
        criteria,
        function (acc, item) {

          // Once an error, always an error.
          if (iserror(acc)) { return acc; }

          // Once `false` or #error! is found always return previously value.
          if (acc === 0 || acc === false) { return false; }

          // find the value if a literal or deferred value.
          var val = isfunction(item) ? item() : item;

          // return `#VALUE!` if not true, false, 1 or 0.
          if (!(val === true || val === false || val === 1 || val === 0)) {
            return error$2.value;
          }

          // Return true when value is true or 1.
          return val === true || val === 1;
        }, undefined);
    }

    // Copyright 2015 JC Fisher

    // ISNA returns true when the value is `#NA!`
    function isnan(value) {
      if (Number.isNaN) { return Number.isNaN(value); }
      return typeof value === 'number' && isNaN(value);
    }

    // Returns true when the value is a falsy value.
    // https://developer.mozilla.org/en-US/docs/Glossary/Falsy
    function isfalsy(value) {
        return (
          value === false ||
          value === 0 ||
          value === '' ||
          typeof value === 'undefined' ||
          value === null ||
          isnan(value)
        )
    };

    // Returns true when the value is not falsey
    // https://developer.mozilla.org/en-US/docs/Glossary/Truthy
    function istruthy(value) {
        return !isfalsy(value)
    };

    // This function provides if-elseif-else.
    // branch( test, result_if_true, [test2, result_if_true, default_result] ).
    function branch() {
      var cases = [], len = arguments.length;
      while ( len-- ) cases[ len ] = arguments[ len ];


      var resolved = false

      // Reduce all cases into a value.
      return reduce( cases, function(acc, item, index) {
        var val;

        // Return previously resolved result.
        if (resolved === true) { return acc }

        // Handle default case.
        if ( index === cases.length - 1 ) {
          // There is no last item.
          if (index % 2 === 1) { return; }

          // return the last item.
          return isfunction(item) ? item() : item;
        }

        // Check if condition is true.
        if (index % 2 === 0 && (
            (isfunction(item) && istruthy(item()) ) ||
            (!isfunction(item) && istruthy(item)))) {
          resolved = true
          val = cases[index+1]
          return isfunction(val) ? val() : val;
        }

        return acc;

      }, undefined)

    }

    // Copyright 2015 JC Fisher

    // ISTEXT returns true when the value is a string.
    function istext(value) {
        return 'string' === typeof(value);
    };

    // Copyright 2015 JC Fisher

    // ISBLANK returns true when the value is undefined or null.
    function isblank(value) {
        return typeof value === 'undefined' || value === null;
    };

    // Copyright 2015 JC Fisher

    // ISARRAY returns true when the value is an aray.
    function isarray(value) {
      return Object.prototype.toString.call( value ) === '[object Array]'
    }

    // Copyright 2015 JC Fisher

    // EQ compares two values and returns a boolean value.
    function eq(a,b) {

      if (typeof a === "string" && typeof b === "string") {
        // String comparisions are case-insensitive when both are string values.
        return a.toLowerCase() === b.toLowerCase()
      } else {
        // Strict equivalence as the default when non-string values are present.
        return a === b;
      }
    }

    // INT returns true when a needle is found in a list.
    function some(needle, list) {

      // Return `#NA!` when the needle and list are blank.
      if ( isblank(needle) && isblank(list) ) {
        return error$2.na;
      }

      // Return `#NA!` when the list is not an array.
      if (!isarray(list)) {
        return error$2.na;
      }

      // Return true when some of the values match the needle.
      return list.some(function (n) { return eq(n, needle); } )
    }

    // Ponyfill or Object.assign with empty initial object.
    function assign(initial) {
      var list = [], len = arguments.length - 1;
      while ( len-- > 0 ) list[ len ] = arguments[ len + 1 ];

      var func = Object.assign || assign;
      return func.apply(void 0, [ {}, initial ].concat( list ));
    }

    var WALKERCONFIGDEFAULT = {
      upCase: true,
      visit: function () { return undefined; },
      labelEQ: "=",
      labelNE: "<>",
      labelGT: "<",
      labelGTE: "<=",
      labelLT: ">",
      renderMINUS: function () { return "-"; },
      renderPLUS: function () { return "+"; },
      renderEQ: function () { return " = "; },
      renderNE: function () { return " <> "; },
      renderGT: function () { return " > "; },
      renderGTE: function () { return " >= "; },
      renderLT: function () { return " < "; },
      renderLTE: function () { return " <= "; },
      renderADD: function () { return " + "; },
      renderSUBTRACT: function () { return " - "; },
      renderMULTIPLY: function () { return " * "; },
      renderDIVIDE: function () { return " / "; },
      renderPOWER: function () { return " ^ "; },
      renderCONCAT: function () { return " & "; },
      renderGroupBegin: function () { return "("; },
      renderGroupEnd: function () { return ")"; },
      renderGroup: function (config, g, depth) {
        return ("" + (config.renderGroupBegin(config, g, depth)) + (walk(
          config,
          g.exp,
          depth + 1
        )) + (config.renderGroupEnd(config, g, depth)));
      },
      renderFunctionBegin: function (config, f, depth) { return config.upCase ? f.name.toUpperCase() : f.name; },
      renderFunctionEnd: function (config, f, depth) { return ")"; },
      renderFunction: function (config, f, depth) { return ((config.renderFunctionBegin(config, f, depth)) + "(" + (f.args
          .map(function (d) { return walk(config, d, depth + 1); })
          .join(", ")) + (config.renderFunctionEnd(config, f, depth))); },
      renderOperator: function (config, ref, depth) {
            var subtype = ref.subtype;
            var operands = ref.operands;

            return branch(
          operands.length == 1,
          function () { return ("" + (branch(
              subtype == "prefix-minus",
              config.renderMINUS(config, depth),
              subtype == "prefix-plus",
              config.renderPLUS(config, depth)
            )) + (walk(config, operands[0], depth + 1))); },
          operands.length === 2,
          function () { return ("" + (walk(config, operands[0], depth + 1)) + (branch(
              subtype == "infix-eq",
              config.renderEQ(config, { operands: operands }, depth),
              subtype == "infix-ne",
              config.renderNE(config, { operands: operands }, depth),
              subtype == "infix-gt",
              config.renderGT(config, { operands: operands }, depth),
              subtype == "infix-gte",
              config.renderGTE(config, { operands: operands }, depth),
              subtype == "infix-lt",
              config.renderLT(config, { operands: operands }, depth),
              subtype == "infix-lte",
              config.renderLTE(config, { operands: operands }, depth),
              subtype == "infix-add",
              config.renderADD(config, { operands: operands }, depth),
              subtype == "infix-subtract",
              config.renderSUBTRACT(config, { operands: operands }, depth),
              subtype == "infix-multiply",
              config.renderMULTIPLY(config, { operands: operands }, depth),
              subtype == "infix-divide",
              config.renderDIVIDE(config, { operands: operands }, depth),
              subtype == "infix-power",
              config.renderPOWER(config, { operands: operands }, depth),
              subtype == "infix-concat",
              config.renderCONCAT(config, { operands: operands }, depth)
            )) + (walk(config, operands[1], depth + 1))); }
        );
},
      renderRangeBetween: function () { return ":"; },
      renderRange: function (config, ref, depth) {
            var topLeft = ref.topLeft;
            var bottomRight = ref.bottomRight;

            return topLeft.name + '@' + bottomRight.name;

//            return 'RV("'+topLeft.name+'","'+bottomRight.name+'")';

//            return ("" + (walk(config, topLeft, depth)) + (config.renderRangeBetween(
//          config,
//          { topLeft: topLeft, bottomRight: bottomRight },
//          depth
//        )) + (walk(config, bottomRight, depth)));
},
      renderVariable: function (config, ref, depth) {
            var scope = ref.scope;
            var name = ref.name;

            return ("" + (scope ? scope + "!" : "") + name);
},
      renderString: function (value) { return ("\"" + value + "\""); },
      renderNumber: function (value) { return value.toString(); },
      renderBoolean: function (value) { return (value ? "TRUE" : "FALSE"); },
      renderValue: function (config, ref, depth) {
        var subtype = ref.subtype;
        var items = ref.items;
        var value = ref.value;

        return branch(
          subtype === "string",
          function () { return config.renderString(value); },
          subtype === "number",
          function () { return config.renderNumber(value); },
          subtype === "boolean",
          function () { return config.renderBoolean(value); },
          subtype === "array",
          function () { return ("" + (config.renderArray(config, { items: items }, depth))); }
        );
      },
      renderArray: function (config, ref, depth) {
            var items = ref.items;

            return "{" +
        items.map(function (d) { return config.renderValue(config, d, depth + 1); }).join(",") +
        "}";
},
      renderRule: function (config, ast, depth) {
        if ( depth === void 0 ) depth = 0;

        var type = ast.type;

        var renderGroup = config.renderGroup;
        var renderFunction = config.renderFunction;
        var renderOperator = config.renderOperator;
        var renderVariable = config.renderVariable;
        var renderValue = config.renderValue;
        var renderRange = config.renderRange;

        config.visit(config, ast, depth);
        config.walk = walk;

        return branch(
          type === "group",
          function () { return renderGroup(config, ast, depth); },
          type === "function",
          function () { return renderFunction(config, ast, depth); },
          type === "operator",
          function () { return renderOperator(config, ast, depth); },
          type === "variable",
          function () { return renderVariable(config, ast, depth); },
          type === "value",
          function () { return renderValue(config, ast, config, depth); },
          type === "range",
          function () { return renderRange(config, ast, config, depth); }
        );
      }
    };

    function runFunc(name, config, ref, depth) {
      var operands = ref.operands;
      var subtype = ref.subtype;

      return (name + "(" + (walk(config, operands[0], depth + 1)) + ", " + (walk(
        config,
        operands[1],
        depth + 1
      )) + ")");
    }
    var WALKERCONFIGFP = assign(WALKERCONFIGDEFAULT, {
      renderOperator: function (config, ref, depth) {
            var subtype = ref.subtype;
            var operands = ref.operands;

            return branch(
          subtype == "prefix-minus",
          ("MINUS(" + (walk(config, operands[0], depth + 1)) + ")"),
          subtype == "prefix-plus",
          ("PLUS(" + (walk(config, operands[0], depth + 1)) + ")"),
          subtype == "infix-eq",
          runFunc("EQ", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-ne",
          runFunc("NE", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-gt",
          runFunc("GT", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-gte",
          runFunc("GTE", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-lt",
          runFunc("LT", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-lte",
          runFunc("LTE", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-add",
          runFunc("ADD", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-subtract",
          runFunc("SUBTRACT", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-multiply",
          runFunc("MULTIPLY", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-divide",
          runFunc("DIVIDE", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-power",
          runFunc("POWER", config, { operands: operands, subtype: subtype }, depth),
          subtype == "infix-concat",
          runFunc("CONCATENATE", config, { operands: operands, subtype: subtype }, depth)
        );
}
    });

    var WALKERCONFIGJS = assign(WALKERCONFIGFP, {
      renderVariable: function (config, v, depth) {
        return v.scope
          ? ("context.get(\"" + (v.name) + "\", \"" + (v.scope) + "\")")
          : ("context.get(\"" + (v.name) + "\")");
      },
      renderFunctionBegin: function (config, f) { return ("Formula." + (WALKERCONFIGFP.renderFunctionBegin(config, f))); },
      renderArray: function (config, items, depth) { return "[" + items.map(function (d) { return config.walk(config, d, depth + 1); }) + "]"; },
      renderValue: function (config, ref, depth) {
        var subtype = ref.subtype;
        var items = ref.items;
        var value = ref.value;

        return branch(
          subtype === "string",
          function () { return ("\"" + value + "\""); },
          subtype === "number",
          function () { return ("" + value); },
          subtype === "boolean",
          function () { return (value ? "true" : "false"); },
          subtype === "array",
          function () { return config.renderArray(config, items, depth); }
        );
      }
    });

    function walk(config, astOrExp, depth) {
      if ( depth === void 0 ) depth = 0;

      var ast = astOrExp;

      if (!ast) {
        return;
      }
      if (istext(astOrExp)) {
        ast = PARSE(astOrExp);
      }

      return config.renderRule(config, ast, depth);
    }

    // return builder
    var WALKER = function (config) {
        if ( config === void 0 ) config = WALKERCONFIGDEFAULT;

        return function (ast) { return walk(config, ast); };
};

    // Copyright 2015 JC Fisher

    // map an array to a new array
    function map(array, func) {
      return array.map(func)
    }

    // UNIQUE reduces an `array` into an array without duplicate values.
    function unique(array) {
      return reduce( array, function(p, c) {
        if (p.indexOf(c) < 0) { p.push(c); }
        return p;
      }, [])
    }

    var compiledNumber = 0;

    function compile(exp) {
      var ast = exp,
        jsCode,
        functionCode,
        f,
        suppress = false,
        precedents = [],
        requires = [],
        namespace = "funcs.";

      // convert to AST when string provided
      if (typeof ast === "string") {
        ast = PARSE(exp);
      }

      var walk1 = WALKER(WALKERCONFIGFP);
      var walk2 = WALKER(
        assign(WALKERCONFIGJS, {
          visit: function (config, node, depth) {
            if (node.type === "variable") {
              precedents.push(node);
            }
            if (node.type === "function") {
              requires.push(node.name);
            }
          }
        })
      );

      // Walk the AST and convert operators to functions.
      var fpExp = walk1(ast);
      // Walk result and convert to JSCode.
      var code = walk2(fpExp);

      var id = compiledNumber++;

      precedents = unique(precedents.map(JSON.stringify)).map(JSON.parse);
      requires = unique(requires.map(JSON.stringify)).map(JSON.parse);

      f = new Function(
        "context",
        "Formula",
        ("/* formula: " + exp + " */\nreturn " + code + ";\n//# sourceURL=formula_" + id + "\n")
      );

      f.id = id;

      f.exp = exp;
      f.exp = fpExp;
      f.exp = exp;
      f.ast = ast;
      f.code = code;
      f.precedents = precedents;
      f.requires = requires;

      return f;
    }

    // m is a cache of compiled expressions.
    var m = {};

    // Execute a formula expression
    function run(exp, params) {
      if ( params === void 0 ) params = {};

      // if the exp is a function then return it immediately.
      if (isfunction(exp)) { return exp; }

      if (!istext(exp)) { return error$2.na; }

      // check cached and shortcut if appropriate.
      if (m.hasOwnProperty(exp)) {
        // reload the compiled function.
        compiled = m[exp];
      } else {
        // compile the expression.
        var compiled = compile(exp);

        // cache the compiled function.
        m[exp] = compiled;
      }

      var locals = assign({}, params);

      // Default get for plain object.
      if (typeof locals.get !== "function") {
        locals.get = function (name, scope) {
          if (istext(scope)) {
            return locals[scope] ? locals[scope][name] : undefined;
          }
          return locals[name];
        };
      }

      return compiled(locals, funcs);
    }

    // CHOOSE accepts an index and a list of items. It returns the item that corresponds to the index.
    function choose(index) {
      var items = [], len = arguments.length - 1;
      while ( len-- > 0 ) items[ len ] = arguments[ len + 1 ];


      // Return `#NA!` if index or items are not provided.
      if (!index || items.length === 0) {
        return error$2.na;
      }

      // Return `#VALUE!` if index is less than 1 or greater than 254.
      if (index < 1 || index > 254) {
        return error$2.value;
      }

      // Return `#VALUE!` if number of items is less than index.
      if (items.length < index) {
        return error$2.value;
      }

      // Return the item.
      return items[index-1];
    }

    // CHOOSE accepts an index and a list of items. It returns the item that corresponds to the index.
    function SWITCH() {
      var arguments$1 = arguments;

      var result;
      if (arguments.length > 0) {
        var targetValue = arguments[0];
        var argc = arguments.length - 1;
        var switchCount = Math.floor(argc / 2);
        var switchSatisfied = false;
        var defaultClause = argc % 2 === 0 ? null : arguments[arguments.length - 1];

        if (switchCount) {
          for (var index = 0; index < switchCount; index++) {
            if (targetValue === arguments$1[index * 2 + 1]) {
              result = arguments$1[index * 2 + 2];
              switchSatisfied = true;
              break;
            }
          }
        }

        if (!switchSatisfied && defaultClause) {
          result = defaultClause;
        }
      }

      return result;
    }

    // NOT negates a `value`
    function not(value) {
      return (value !== true && value !== false && value !== 1 && value !== 0) ?
      error$2.value :
      !value
    }

    // Returns the composition of NOT(AND(...))
    function nor() {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];

      return not(and.apply(void 0, args));
    }

    // Returns true when any of the criteria are true or 1, defaults to false.
    function or() {
      var criteria = [], len = arguments.length;
      while ( len-- ) criteria[ len ] = arguments[ len ];

      return reduce( criteria, function (acc, item) {

        // If accumulator is already true then it's still true.
        if (acc === true) { return true; }

        // Determine the value by resolving thunks if needed.
        var value = isfunction(item) ? item() : item;

        // Return true when value is true or 1.
        return value === true || value === 1;

      }, false)
    }

    function nor$1() {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];

      return not(or.apply(void 0, args));
    }

    // FLATTEN converts a nested array into a flattened array. It only supports one
    // level of nesting.
    function flatten(ref){

      if (!isarray(ref)) {
        return error$2.value;
      }

      return reduce( ref, function(a, b) {
        return a.concat(b);
      }, []);
    }

    // XOR computes the exclusive or for a given set of `values`.
    function xor() {
        var values = [], len = arguments.length;
        while ( len-- ) values[ len ] = arguments[ len ];

        return !!( reduce( flatten(values), function (a,b) {
          if (b) {
            return a+1
          }
          return a
        }, 0) & 1)
    }

    // NE returns true when a is not equal to b.
    function ne(a,b) {
      return !eq(a, b)
    }

    // ISREF returns true when the value is a reference.
    function isref(value) {
      if (!value) { return false }
      return value._isref === true
    }

    function gt(a,b) {
      if ( isref(a) && isref(b) ) {
        return error$2.na;
      } else if ( isarray(a) && isarray(b) ) {
        return error$2.na;
      } else if ( isref(a) || isarray(a) ) {
        return map( a, function (d) { return d > b; } );
      } else if ( isref(b) || isarray(b) ) {
        return map( a, function (d) { return d > a; } );
      } else {
        return a > b;
      }
    }

    function gte(a,b) {
      if ( isref(a) && isref(b) ) {
        return error.na;
      } else if ( isarray(a) && isarray(b) ) {
        return error.na;
      } else if ( isref(a) || isarray(a) ) {
        return map( a, function (d) { return d >= b; } );
      } else if ( isref(b) || isarray(b) ) {
        return map( a, function (d) { return d >= a; } );
      } else {
        return a >= b;
      }
    }

    // LT compares two values and returns true when a is less than b.
    function lt(a,b) {
      if ( isref(a) && isref(b) ) {
        return error.na;
      } else if ( isarray(a) && isarray(b) ) {
        return error.na;
      } else if ( isref(a) || isarray(a) ) {
        return map( a, function (d) { return d < b; } );
      } else if ( isref(b) || isarray(b) ) {
        return map( a, function (d) { return d < a; } );
      } else {
        return a < b;
      }
    }

    // LT compares two values and returns true when a is less than or equal to b.
    function lte(a,b) {
      if ( isref(a) && isref(b) ) {
        return error.na;
      } else if ( isarray(a) && isarray(b) ) {
        return error.na;
      } else if ( isref(a) || isarray(a) ) {
        return map( a, function (d) { return d <= b; } );
      } else if ( isref(b) || isarray(b) ) {
        return map( a, function (d) { return d <= a; } );
      } else {
        return a <= b;
      }
    }

    // IFBLANK return the `value` if non-blank, otherwise it returns `value_if_blank`.
    function ifblank(value, value_if_blank) {
        return isblank(value) ? value_if_blank : value;
    }

    // ISEMPTY returns true when the value is blank, is an empty array or when it
    // is an empty string.
    function isempty(value) {
        return (
          isblank(value) ||
          isarray(value) && value.length === 0 ||
          istext(value) && value === ''
        );
    };

    // IFBLANK return the `value` if empty, otherwise it returns `value_if_empty`.
    function ifempty(value, value_if_empty) {
        return isempty(value) ? value_if_empty : value;
    }

    // IFBLANK return the `value` if error, otherwise it returns `value_if_error`.
    function iferror(value, value_if_error) {
        if ( value_if_error === void 0 ) value_if_error=null;

        return iserror(value) ? value_if_error : value;
    }

    // IFBLANK return the `value` if `#NA!`, otherwise it returns `value_if_na`.
    function ifna(value, value_if_na) {
        return value === error$2.na ? value_if_na : value;
    }

    // Copyright 2015 JC Fisher

    // returns true if true or false
    function isboolean(val) {
        return val === true || val === false
    };

    // Copyright 2015 JC Fisher

    // ISDATE returns true when the `value` is a JavaScript date object.
    function isdate(value) {
        return value && Object.prototype.toString.call(value) == '[object Date]';
    };

    // Copyright 2015 JC Fisher

    // ISEMAIL returns true when the `value` matches the regex.
    function isemail(value) {
      // credit to http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
      var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(value);
    };

    // Copyright 2015 JC Fisher

    // ISEVEN returns true when the value is even.
    function iseven(value) {
        return !(Math.floor(Math.abs(value)) & 1);
    }

    // Copyright 2015 JC Fisher

    // Shared constants
    var d1900 = new Date(1900, 0, 1);
    var JulianOffset = 2415019;
    var SecondsInMinute = 60;
    var SecondsInHour = 3600;
    var SecondsInDay = 86400;
    var MilliSecondsInDay = 86400000;
    var AllowedDates = {H: "h]", M: "m]", MM: "mm]", S: "s]", SS: "ss]"};
    var DayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    var DayNames3 = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
    var MonthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    var MonthNames3 = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
    var AM = "AM";
    var AM1 = "A";
    var PM = "PM";
    var PM1 = "P";
    var τ = 6.28318530717958;
    var MaxCols = 16384;
    var SeparatorChar = ",";
    var DecimalChar = ".";
    var DefaultCurrency = "$";
    var AllowedColors = {
        BLACK: "#000000",
        BLUE: "#0000FF",
        CYAN: "#00FFFF",
        GREEN: "#00FF00",
        MAGENTA: "#FF00FF",
        RED: "#FF0000",
        WHITE: "#FFFFFF",
        YELLOW: "#FFFF00"
      };

    // PARSEDATE converts a value into a Date object.
    function parsedate(val) {
      /* *******************
      Extracted from Social Calc

      convert_date_julian_to_gregorian(juliandate)

      ymd->{}
      .year
      .month
      .day

      From: http://aa.usno.navy.mil/faq/docs/JD_Formula.html
      Uses: Fliegel, H. F. and van Flandern, T. C. (1968). Communications of the ACM, Vol. 11, No. 10 (October, 1968).
      Translated from the FORTRAN

      ************************* */
      function convert_date_julian_to_gregorian(juliandate) {
        var L, N, I, J, K;

        L = juliandate + 68569;
        N = Math.floor(4 * L / 146097);
        L = L - Math.floor((146097 * N + 3) / 4);
        I = Math.floor(4000 * (L + 1) / 1461001);
        L = L - Math.floor(1461 * I / 4) + 31;
        J = Math.floor(80 * L / 2447);
        K = L - Math.floor(2447 * J / 80);
        L = Math.floor(J / 11);
        J = J + 2 - 12 * L;
        I = 100 * (N - 49) + I + L;

        return new Date(I, J - 1, K);
      }

      if (val instanceof Error) {
        return val;
      } else if (isdate(val)) {
        return val;
      } else if (typeof val === "number") {
        // val is assumed to be serial number.
        return convert_date_julian_to_gregorian(Math.floor(val + JulianOffset));
      } else if (typeof val === "string") {
        var timestamp = Date.parse(val);
        if (isnan(timestamp)) {
          return error$2.value;
        }
        return new Date(timestamp);
      }

      return error$2.value;
    }

    function isleapyear(val) {
        var date = parsedate(val);
        var year = date.getFullYear();
        return (((year % 4 === 0) && (year % 100 !== 0)) ||
                (year % 400 === 0));
    }

    // Copyright 2015-2018 JC Fisher

    var aCode = 97;
    var zCode = 122;

    function isLowerCaseChar(ch) {
      var chCode = ch.charCodeAt();
      return chCode >= aCode && chCode <= zCode;
    }

    function islowercase(str) {
      for (var i in str) {
        if (!isLowerCaseChar(str[i])) { return false; }
      }
      return true;
    }

    // Copyright 2015 JC Fisher

    // isobject returns true when `value` is an object or function.
    function isobject(value) {
      var type = typeof value;
      return !!value && (type == 'object' || type == 'function');
    };

    // ISNA returns true when the value is `#NA!`
    function isna(value) {
      return value === error$2.na;
    }

    // Copyright 2015 JC Fisher

    // Returns true when the value is a finite number.
    function isnumber(value) {
        return typeof(value) === 'number' && !isNaN(value) && isFinite(value);
    }

    // Copyright 2015 JC Fisher

    // ISODD returns true when the value is odd.
    function isodd(value) {
      return !!(Math.floor(Math.abs(value)) & 1);
    }

    function isoweeknum(date) {
        date = parsedate(date);

        if (date instanceof Error) {
            return date;
        }

        date.setHours(0, 0, 0);
        date.setDate(date.getDate() + 4 - (date.getDay() || 7));
        var yearStart = new Date(date.getFullYear(), 0, 1);
        return Math.ceil((((date - yearStart) / MilliSecondsInDay) + 1) / 7);
    };

    // Copyright 2015-2018 JC Fisher

    var ACode = 65;
    var ZCode = 90;

    function isUpperCaseChar(ch) {
      var chCode = ch.charCodeAt();
      return chCode >= ACode && chCode <= ZCode;
    }

    function isuppercase(str) {
      for (var i in str) {
        if (!isUpperCaseChar(str[i])) { return false; }
      }
      return true;
    }

    // Copyright 2015 JC Fisher

    // ISURL returns true when the value matches the regex for a uniform resource locator.
    function isurl(str){
      // credit: http://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-an-url
      var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
      '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ // domain name
      '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
      '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
      '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
      '(\\#[-a-z\\d_]*)?$','i'); // fragment locator
      return pattern.test(str);
    }

    // Returns true when the value is a whole number
    function iswholenumber(value) {
        return isnumber(value) && (value % 1 === 0);
    }

    // ADD calculates the sum of two numbers.
    function add() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];


      // Return `#NA!` if 2 arguments are not provided.
      if (values.length !== 2) {
        return error$2.na;
      }

      // decompose values into a and b.
      var a = values[0];
      var b = values[1];

      // Return `#VALUE!` if either a or b is not a number.
      if (!isnumber(a) || !isnumber(b)) {
        return error$2.value
      }

      // Return the sum.
      return a + b
    }

    // SUBTRACT calculates the difference of two numbers.
    function subtract() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];


      // Return `#NA!` if 2 arguments are not provided.
      if (values.length !== 2) {
        return error$2.na;
      }

      // decompose values into a and b.
      var a = values[0];
      var b = values[1];

      // Return `#VALUE!` if either a or b is not a number.
      if (!isnumber(a) || !isnumber(b)) {
        return error$2.value
      }

      // Return the difference.
      return a - b
    }

    // MULTIPLY calculates the product of two numbers.
    function multiply() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];


      // Return `#NA!` if 2 arguments are not provided.
      if (values.length !== 2) {
        return error$2.na;
      }

      // decompose values into a and b.
      var a = values[0];
      var b = values[1];

      // Return `#VALUE!` if either a or b is not a number.
      if (!isnumber(a) || !isnumber(b)) {
        return error$2.value
      }

      // Return the product
      return a * b
    }

    // DIVIDE calculates the product of two numbers.
    function divide() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];


      // Return `#NA!` if 2 arguments are not provided.
      if (values.length !== 2) {
        return error$2.na;
      }

      // decompose values into a and b.
      var a = values[0];
      var b = values[1];

      // You cannot divide a number by 0.
      if (b === 0) {
        return error$2.div0
      }

      // Return `#VALUE!` if either a or b is not a number.
      if (!isnumber(a) || !isnumber(b)) {
        return error$2.value
      }

      // Return the product
      return a / b
    }

    // ABS computes absolute value of a number
    function abs(value) {

      // Return `#VALUE!` if not number
      if (!isnumber(value)) {
        return error$2.value;
      }

      // Use built-in Math.abs
      return Math.abs(value);
    }

    // ACOS computes the inverse cosine of a number
    function acos(value) {

      // Return `#VALUE!` if not number
      if (!isnumber(value)) {
        return error$2.value;
      }

      // Use built-in Math.acos
      return Math.acos(value);

    }

    // Returns the hyperbolic inverser cosine of a value.
    function acosh(value) {

      if (!isnumber(value)) {
        return error$2.value;
      }

      return Math.log(value + Math.sqrt(value * value - 1));
    };

    // Return the arccotangent of a given number.
    function acot(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return Math.atan(1 / number);
    }

    // Return the arccotangent of a given number
    function acoth(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return 0.5 * Math.log((number + 1) / (number - 1));
    }

    // Return the inverse sin of a given number
    function asin(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return Math.asin(number);
    }

    // Return the inverse hyperbolic sin of a given number
    function asinh(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return Math.log(number + Math.sqrt(number * number + 1));
    }

    // Return the arctangent (in radians) of the given number
    function atan(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return Math.atan(number);
    }

    // Return the arctangent (in radians) of the given number
    function atan$1(x, y) {

      // Ensure value is a number
      if (!isnumber(x) || !isnumber(y) ) {
        return error$2.value;
      }

      // Compute value
      return Math.atan2(x, y);
    }

    // Return the arctangent (in radians) of the given number
    function atan$2(x) {

      // Ensure value is a number
      if (!isnumber(x)) {
        return error$2.value;
      }

      // Compute value
      return Math.log((1+x)/(1-x)) / 2;
    }

    // COS returns the cosine of a value.
    function cos(value) {

      // Return `#VALUE!` when value is not a number.
      if (!isnumber(value)) {
        return error$2.value;
      }

      return Math.cos(value);

    }

    // Converts radians into degrees.
    function degrees(number) {

      // Ensure value is a number
      if (!isnumber(number)) {
        return error$2.value;
      }

      // Compute value
      return number * 180 / Math.PI;
    }

    // PI returns half the universal circle constant
    function pi$1() {
      return τ / 2;
    }

    // POWER computes the power of a value and nth degree.
    function power() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];


      // Return `#NA!` if 2 arguments are not provided.
      if (values.length !== 2) {
        return error$2.na;
      }

      // decompose values into a and b.
      var val = values[0];
      var nth = values[1];

      // Return `#VALUE!` if either a or b is not a number.
      if (!isnumber(val) || !isnumber(nth)) {
        return error$2.value
      }

      // Compute the power of val to the nth.
      return Math.pow(val, nth);
    }

    // Copyright 2015 JC Fisher

    // CONVERT a number to a fixed precision.
    function round(number, precision) {
      return +number.toFixed(precision);
    }

    // Copyright 2015 JC Fisher

    // ROUNDUP converts a number to a fixed precision by rounding up.
    function roundup(number, precision) {
      var factors = [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000];
      var factor = factors[precision];
      if (number > 0) {
        return Math.ceil(number * factor) / factor;
      } else {
        return Math.floor(number * factor) / factor;
      }
    }

    // SIN calculates the sinine of a value.
    function sin(value) {

      if (!isnumber(value)) {
        return error$2.value;
      }

      return Math.sin(value);

    }

    // TAN computes the tagent of a value.
    function tan(value) {

      if (!isnumber(value)) {
        return error$2.value;
      }

      return Math.tan(value);

    }

    // TAU returns the universal circle constant
    function tau() {
      return τ;
    }

    // Copyright 2015 JC Fisher

    // remove decimal part of number
    function trunc(val) {
      return val|0;
    }

    // Copyright 2015 JC Fisher

    // CHAR convert number into character (e.g 65 => 'A')
    function char(number) {
      return String.fromCharCode(number);
    }

    // convert snakecase to camelcase.
    function camelcase(value) {

      // Return `#VALUE!` if not text input.
      if (!istext(value)) {
        return error$2.value;
      }

      // converts -c into C and _c in C for every matched character.
      return value.replace(/-+(.)?/g, function(match, chr) {
        return chr ? chr.toUpperCase() : '';
      }).replace(/_+(.)?/g, function(match, chr) {
        return chr ? chr.toUpperCase() : '';
      });
    }

    // CODE accepts text and optionally index (default 1) returning the character code.
    function code(text, index) {
      if ( text === void 0 ) text='';
      if ( index === void 0 ) index=1;

      if (index < 1) { return error$2.na }
      if (text.length < index) { return error$2.value }
      return text.charCodeAt(index-1);
    }

    // CONCATENATE reduces a list of values into a single string.
    function concatenate() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];

      // Combine into a single string value
      return reduce( values, function (acc, item) { return ("" + acc + item); }, '' )
    }

    // Exact compares two values and only returns true if they meet strict equivalence.
    function exact(a, b) {
      return a === b
    }

    // FIND searches for text within a string
    function find(find_text, within_text, position) {
      if ( within_text === void 0 ) within_text='';
      if ( position === void 0 ) position=1;


      // Find the position of the text
      position = within_text.indexOf(find_text, position - 1)

      // If found return the position as base 1.
      return position === -1 ? error$2.value : position+1
    }

    // combine a array of strings/numbers into a single string
    function join(list, delim) {
      if ( delim === void 0 ) delim=', ';


      // all values must be string or number
      if (list.some(function (d) { return typeof d !== 'string' && typeof d !== 'number'; })) {
        return error$2.value
      }

      // defer to JS implementation
      return list.join(delim)
    }

    // SERIAL convert a date object into a serial number.
    function serial(date) {
      // Credit: https://github.com/sutoiku/formula.js/
      if (!isdate(date)) { return error$2.na }
      var diff = Math.ceil((date - d1900) / MilliSecondsInDay)
      return diff + ( diff > 59 ? 2 : 1)
    }

    // N converts a `value` to a number. It supports numbers, true, false and dates.
    function n(value) {

      // Pass numbers and errors back out.
      if (isnumber(value) || iserror(value)) {
        return value;
      }

      // Convert dates to serial number.
      if (value instanceof Date) {
        return serial(value);
      }

      // Convert true to 1
      if (value === true) {
        return 1;
      }

      // Convert false to 0
      if (value === false) {
        return 0;
      }

      // Return 0 in all other cases.
      return 0;

    }

    function left(text, number) {
      // For blank text value, return empty string.
      if (isblank(text)) {
        return ''
      }
      // When number is invalid, return original value.
      if (!n(+number)) {
        return text
      }
      // Return truncated string value.
      return text.substring( 0, number )

    }

    // LEN returns the size of a string or array.
    function len(text) {
      if (arguments.length === 0) {
        return error$2.error;
      }

      if (typeof text === 'string') {
        return text.length;
      }

      if (text.length) {
        return text.length;
      }

      return error$2.value;
    };

    // LOWER converts `value` to lower case
    function lower(value) {
      if (!istext(value)) { return error$2.value }
      return value.toLowerCase()
    }

    // Copyright 2015 JC Fisher

    // SPLIT `text` given a `delimiter`.
    function split(text, delimiter) {
      return text.split(delimiter)
    }

    // Convert a text value into a number value.
    function numbervalue(text, decimal_separator, group_separator)  {
      decimal_separator = decimal_separator || '.'
      group_separator = group_separator || ','

      // Return error when text is error
      if (iserror(text)) {
        return text
      }

      // Return `#VALUE!` when text is empty
      if (isempty(text)) {
        return error$2.value
      }

      // Return the value when it is already a number.
      if (isnumber(text)) {
        return text
      }

      var foundDecimal = false,
      len = text.length-1

      if (text.length === 1) {
        if ( code( text, 0 ) < 48 ||  code( text, 0 ) > 57 ) {
          return error$2.value
        }
        return +text
      }

      return reduce( split(text, ''), function (acc, item, index) {
        if (acc === error$2.value) {
          return error$2.value;
        } else if (len === index) {
          if (item === '%') {
            return +acc / 100
          }
          return +acc.concat(item)
        } else if (item === decimal_separator) {
          if (foundDecimal) { return error$2.value; }
          foundDecimal = true
          return acc.concat('.')
        } else if( item === group_separator ) {
          return acc
        // check if between 0 and 9 ascii codes
        } else if (item.charCodeAt(0) < 48 ||  item.charCodeAt(0) > 57) {
          return error$2.value
        }

        return acc.concat(item);

      })
    };

    // PARSEBOOL converts a truthy value into a boolean value.
    function parsebool(val) {

      if (val instanceof Error) {
        return val;
      } else if (typeof val === 'boolean') {
        return val;
      } else if (typeof val === 'number') {
        return val !== 0;
      } else if (typeof val === 'string') {
        var up = val.toUpperCase();
        if (up === 'TRUE' || up === 'FALSE') {
          return up === 'TRUE';
        }
      }

      return error$2.value;
    }

    // parse querystring into object
    function parsequery(query) {
      if ( query === void 0 ) query='';


      if (typeof query !== 'string') {
        return error$2.na;
      }

      if (query.length === 0) {
        return {}
      }

      return reduce(
        (query[0] === '?' ? query.substr(1) : query)
        .split('&'),
        function (acc, item) {
          var n = item.split('=');
          var key = decodeURIComponent(n[0]);
          var value = decodeURIComponent(n[1] || '');
          acc[key] = value;
          return acc
        }, {} )
    }

    // PROPER converts text into proper case.
    function proper(text) {
        if (text === undefined || text.length === 0) {
            return error$2.value;
        }
        if (text === true) {
            text = 'TRUE';
        }
        if (text === false) {
            text = 'FALSE';
        }
        if (isnan(text) && typeof text === 'number') {
            return error$2.value;
        }
        if (typeof text === 'number') {
            text = '' + text;
        }

        return text.replace(/\w\S*/g, function(txt) {
            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
        });
    }

    // REPLACE returns a new string after replacing with `new_text`.
    function replace(text, position, length, new_text) {

      if (iserror(position) || iserror(length) ||
      typeof text !== 'string' || typeof new_text !== 'string') {
        return error$2.value;
      }
      return text.substr(0, position - 1) + new_text + text.substr(position - 1 + length);
    }

    // RIGHT pulls a given number of character from the right side of `text`.
    function right(text, number) {

      // For blank text value, return empty string.
      if (isblank(text)) {
        return ''
      }

      // When number is invalid, return original value.
      if (!n(+number)) {
        return text
      }
      // Return truncated string value.
      return text.substring( text.length - number )

    }

    // Copyright 2015 JC Fisher

    // REPT creates string by repeating text a given number of times.
    function rept(text, number) {
      var r = '';
      for (var i = 0; i < number; i++) {
        r += text;
      }
      return r;
    }

    // SEARCH finds text using wildcards ?, *, ~?, and ~*.
    function search(find_text, within_text, position) {
        if (!within_text) { return null; }
        position = (typeof position === 'undefined') ? 1 : position;

        // The SEARCH function translated the find_text into a regex.
        var find_exp = find_text
            .replace(/([^~])\?/g, '$1.')   // convert ? into .
            .replace(/([^~])\*/g, '$1.*')  // convert * into .*
            .replace(/([~])\?/g, '\\?')    // convert ~? into \?
            .replace(/([~])\*/g, '\\*');   // convert ~* into \*

        position = new RegExp(find_exp, "i").exec(within_text);

        if (position) { return position.index + 1 }
        return error$2.value;
    }

    // convert camelcase to snakecase.
    function snakecase(value) {

      // Return `#VALUE!` if not text input.
      if (!istext(value)) {
        return error$2.value;
      }

      // credit: prototype.js
      return value.replace(/::/g, '/')
        .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
        .replace(/([a-z\d])([A-Z])/g, '$1_$2')
        .replace(/-/g, '_')
        .toLowerCase();
    }

    // Copyright 2015 JC Fisher

    // SUBSTITUTE `old_text` with `new_text` a given number of occurrences in `text`.
    function substitute(text, old_text, new_text, occurrence) {
      if (!text || !old_text || !new_text) {
        return text;
      } else if (occurrence === undefined) {
        return text.replace(new RegExp(old_text, 'g'), new_text);
      } else {
        var index = 0;
        var i = 0;
        while (text.indexOf(old_text, index) > 0) {
          index = text.indexOf(old_text, index + 1);
          i++;
          if (i === occurrence) {
            return text.substring(0, index) + new_text + text.substring(index + old_text.length);
          }
        }
      }
    }

    // Copyright 2015 JC Fisher

    // combine a array of strings/numbers into a single string
    function keys(subject) {
      return Object.keys(subject)
    }

    // substituteAll is a lightweight "substitution tags" engine that implement a global substitute for multiple items.
    //
    // The key values in your locals object are replaced. Unlike other templating systems it doesn't specify characters that surround your tokens.
    //
    // The function does not encode HTML entities. Don't use this to generate HTML. There are plently of alternative like underscore.js that do that already.
    //
    // It is equivalent to:
    // ```js
    // locals = { '-first-': 'Joe', '-last-': 'Smith' }
    // substitute( substitute("-first- -last", '-first-',  locals), '-last-', 'Smith', locals)
    // ```
    function substituteAll(content, locals, start, end) {
      if ( start === void 0 ) start='-';
      if ( end === void 0 ) end=start;

      if (!locals) { return content; }
      return reduce( keys(locals), function (p, v) { return substitute(p, ("" + start + v + end), locals[v]); }, content)
    }

    // Creates a new object where all of the keys are surrounded by
    // start and end delimiters.
    function surroundKeys(obj, start, end) {
      if ( start === void 0 ) start='-';

      end = end || start
      return reduce( keys(obj), function (p, v) {
        p[("" + start + v + end)] = obj[v];
        return p;
      }, {})
    }

    var FormatNumber = {};

    FormatNumber.format_definitions = {}; // Parsed formats are stored here globally

    // Other constants

    FormatNumber.commands = {
      copy: 1, color: 2, integer_placeholder: 3, fraction_placeholder: 4, decimal: 5,
      currency: 6, general:7, separator: 8, date: 9, comparison: 10, section: 11, style: 12
    };


    /* *******************

    result = FormatNumber.formatNumberWithFormat = function(rawvalue, format_string, currency_char)

    ************************* */

    FormatNumber.formatNumberWithFormat = function(rawvalue, format_string, currency_char) {

      var scfn = FormatNumber;

      var op, operandstr, fromend, cval, operandstrlc;
      var startval, estartval;
      var hrs, mins, secs, ehrs, emins, esecs, ampmstr, ymd;
      var minOK, mpos, mspos;
      var result='';
      var format;
      var section, gotcomparison, compop, compval, cpos, oppos;
      var sectioninfo;
      var i, decimalscale, scaledvalue, strvalue, strparts, integervalue, fractionvalue;
      var integerdigits2, integerpos, fractionpos, textcolor, textstyle, separatorchar, decimalchar;
      var value; // working copy to change sign, etc.

      rawvalue = rawvalue-0; // make sure a number
      value = rawvalue;
      if (!isFinite(value)) { return 'NaN'; }

      var negativevalue = value < 0 ? 1 : 0; // determine sign, etc.
      if (negativevalue) { value = -value; }
      var zerovalue = value == 0 ? 1 : 0;

      currency_char = currency_char || DefaultCurrency;

      FormatNumber.parse_format_string(scfn.format_definitions, format_string); // make sure format is parsed
      format = scfn.format_definitions[format_string]; // Get format structure

      if (!format) { throw 'Format not parsed error.'; }

      section = format.sectioninfo.length - 1; // get number of sections - 1

      // has comparisons - determine which section
      if (format.hascomparison) {
        section = 0; // set to which section we will use
        gotcomparison = 0; // this section has no comparison
        for (cpos=0; ;cpos++) { // scan for comparisons
          op = format.operators[cpos];
          operandstr = format.operands[cpos]; // get next operator and operand

          // at end with no match
          if (!op) {
            // if comparison but no match
            if (gotcomparison) {
              format_string = 'General'; // use default of General
              scfn.parse_format_string(scfn.format_definitions, format_string);
              format = scfn.format_definitions[format_string];
              section = 0;
            }
            break; // if no comparision, matches on this section
          }
          // end of section
          if (op == scfn.commands.section) {
            if (!gotcomparison) { // no comparison, so it's a match
              break;
            }
            gotcomparison = 0;
            section++; // check out next one
            continue;
          }
          // found a comparison - do we meet it?
          if (op == scfn.commands.comparison) {
            i=operandstr.indexOf(':');
            compop=operandstr.substring(0,i);
            compval=operandstr.substring(i+1)-0;
            if ((compop == '<' && rawvalue < compval) ||
            (compop == '<=' && rawvalue <= compval) ||
            (compop == '=' && rawvalue == compval) ||
            (compop == '<>' && rawvalue != compval) ||
            (compop == '>=' && rawvalue >= compval) ||
            (compop == '>' && rawvalue > compval)) {
              break;
            }
            gotcomparison = 1;
          }
        }
      }
      // more than one section (separated by ";")
      else if (section > 0) {
        // two sections
        if (section == 1) {
          if (negativevalue) {
            negativevalue = 0; // sign will provided by section, not automatically
            section = 1; // use second section for negative values
          }
          else {
            section = 0; // use first for all others
          }
        }
        // three sections
        else if (section == 2) {
          if (negativevalue) {
            negativevalue = 0; // sign will provided by section, not automatically
            section = 1; // use second section for negative values
          }
          else if (zerovalue) {
            section = 2; // use third section for zero values
          }
          else {
            section = 0; // use first for positive
          }
        }
      }

      sectioninfo = format.sectioninfo[section]; // look at values for our section

      if (sectioninfo.commas > 0) { // scale by thousands
        for (i=0; i<sectioninfo.commas; i++) {
          value /= 1000;
        }
      }
      if (sectioninfo.percent > 0) { // do percent scaling
        for (i=0; i<sectioninfo.percent; i++) {
          value *= 100;
        }
      }

      decimalscale = 1; // cut down to required number of decimal digits
      for (i=0; i<sectioninfo.fractiondigits; i++) {
        decimalscale *= 10;
      }
      scaledvalue = Math.floor(value * decimalscale + 0.5);
      scaledvalue = scaledvalue / decimalscale;

      if (typeof scaledvalue != 'number') { return 'NaN'; }
      if (!isFinite(scaledvalue)) { return 'NaN'; }

      strvalue = scaledvalue+''; // convert to string (Number.toFixed doesn't do all we need)

      //   strvalue = value.toFixed(sectioninfo.fractiondigits); // cut down to required number of decimal digits
      // and convert to string

      if (scaledvalue == 0 && (sectioninfo.fractiondigits || sectioninfo.integerdigits)) {
        negativevalue = 0; // no "-0" unless using multiple sections or General
      }

      // converted to scientific notation
      if (strvalue.indexOf('e')>=0) {
        return rawvalue+''; // Just return plain converted raw value
      }

      strparts=strvalue.match(/^\+{0,1}(\d*)(?:\.(\d*)){0,1}$/); // get integer and fraction parts
      if (!strparts) { return 'NaN'; } // if not a number
      integervalue = strparts[1];
      if (!integervalue || integervalue=='0') { integervalue=''; }
      fractionvalue = strparts[2];
      if (!fractionvalue) { fractionvalue = ''; }

      // there are date placeholders
      if (sectioninfo.hasdate) {
        // bad date
        if (rawvalue < 0) {
          return '??-???-?? ??:??:??';
        }
        startval = (rawvalue-Math.floor(rawvalue)) * SecondsInDay; // get date/time parts
        estartval = rawvalue * SecondsInDay; // do elapsed time version, too
        hrs = Math.floor(startval / SecondsInHour);
        ehrs = Math.floor(estartval / SecondsInHour);
        startval = startval - hrs * SecondsInHour;
        mins = Math.floor(startval / 60);
        emins = Math.floor(estartval / 60);
        secs = startval - mins * 60;
        decimalscale = 1; // round appropriately depending if there is ss.0
        for (i=0; i<sectioninfo.fractiondigits; i++) {
          decimalscale *= 10;
        }
        secs = Math.floor(secs * decimalscale + 0.5);
        secs = secs / decimalscale;
        esecs = Math.floor(estartval * decimalscale + 0.5);
        esecs = esecs / decimalscale;
        if (secs >= 60) { // handle round up into next second, minute, etc.
          secs = 0;
          mins++; emins++;
          if (mins >= 60) {
            mins = 0;
            hrs++; ehrs++;
            if (hrs >= 24) {
              hrs = 0;
              rawvalue++;
            }
          }
        }
        fractionvalue = (secs-Math.floor(secs))+''; // for "hh:mm:ss.000"
        fractionvalue = fractionvalue.substring(2); // skip "0."

        ymd = parsedate(rawvalue);
        ymd = {
          year: ymd.getFullYear(),
          month: ymd.getMonth() + 1,
          day: ymd.getDate()
        }

        minOK = 0; // says "m" can be minutes if true
        mspos = sectioninfo.sectionstart; // m scan position in ops
        for ( ; ; mspos++) { // scan for "m" and "mm" to see if any minutes fields, and am/pm
          op = format.operators[mspos];
          operandstr = format.operands[mspos]; // get next operator and operand
          if (!op) { break; } // don't go past end
          if (op==scfn.commands.section) { break; }
          if (op==scfn.commands.date) {
            if ((operandstr.toLowerCase()=='am/pm' || operandstr.toLowerCase()=='a/p') && !ampmstr) {
              if (hrs >= 12) {
                if (hrs > 12) { hrs -= 12; }
                ampmstr = operandstr.toLowerCase()=='a/p' ? PM1 : PM; // "P" : "PM";
              }
              else {
                if (hrs === 0) { hrs = 12; }
                ampmstr = operandstr.toLowerCase()=='a/p' ? AM1 : AM; // "A" : "AM";
              }
              if (operandstr.indexOf(ampmstr)<0)
              { ampmstr = ampmstr.toLowerCase(); } // have case match case in format
            }
            if (minOK && (operandstr=='m' || operandstr=='mm')) {
              format.operands[mspos] += 'in'; // turn into "min" or "mmin"
            }
            if (operandstr.charAt(0)=='h') {
              minOK = 1; // m following h or hh or [h] is minutes not months
            }
            else {
              minOK = 0;
            }
          }
          else if (op!=scfn.commands.copy) { // copying chars can be between h and m
            minOK = 0;
          }
        }
        minOK = 0;
        for (--mspos; ; mspos--) { // scan other way for s after m
          op = format.operators[mspos];
          operandstr = format.operands[mspos]; // get next operator and operand
          if (!op) { break; } // don't go past end
          if (op==scfn.commands.section) { break; }
          if (op==scfn.commands.date) {
            if (minOK && (operandstr=='m' || operandstr=='mm')) {
              format.operands[mspos] += 'in'; // turn into "min" or "mmin"
            }
            if (operandstr=='ss') {
              minOK = 1; // m before ss is minutes not months
            }
            else {
              minOK = 0;
            }
          }
          else if (op!=scfn.commands.copy) { // copying chars can be between ss and m
            minOK = 0;
          }
        }
      }

      integerdigits2 = 0; // init counters, etc.
      integerpos = 0;
      fractionpos = 0;
      textcolor = '';
      textstyle = '';
      separatorchar = SeparatorChar;
      if (separatorchar.indexOf(' ')>=0) { separatorchar = separatorchar.replace(/ /g, ' '); }
      decimalchar = DecimalChar;
      if (decimalchar.indexOf(' ')>=0) { decimalchar = decimalchar.replace(/ /g, ' '); }

      oppos = sectioninfo.sectionstart;

      while (op = format.operators[oppos]) { // execute format
        operandstr = format.operands[oppos++]; // get next operator and operand

        if (op == scfn.commands.copy) { // put char in result
          result += operandstr;
        }

        else if (op == scfn.commands.color) { // set color
          textcolor = operandstr;
        }

        else if (op == scfn.commands.style) { // set style
          textstyle = operandstr;
        }

        else if (op == scfn.commands.integer_placeholder) { // insert number part
          if (negativevalue) {
            result += '-';
            negativevalue = 0;
          }
          integerdigits2++;
          if (integerdigits2 == 1) { // first one
            if (integervalue.length > sectioninfo.integerdigits) { // see if integer wider than field
              for (;integerpos < (integervalue.length - sectioninfo.integerdigits); integerpos++) {
                result += integervalue.charAt(integerpos);
                if (sectioninfo.thousandssep) { // see if this is a separator position
                  fromend = integervalue.length - integerpos - 1;
                  if (fromend > 2 && fromend % 3 == 0) {
                    result += separatorchar;
                  }
                }
              }
            }
          }
          if (integervalue.length < sectioninfo.integerdigits
            && integerdigits2 <= sectioninfo.integerdigits - integervalue.length) { // field is wider than value
              if (operandstr == '0' || operandstr == '?') { // fill with appropriate characters
              result += operandstr == '0' ? '0' : ' ';
              if (sectioninfo.thousandssep) { // see if this is a separator position
                fromend = sectioninfo.integerdigits - integerdigits2;
                if (fromend > 2 && fromend % 3 == 0) {
                  result += separatorchar;
                }
              }
            }
          }
          else { // normal integer digit - add it
            result += integervalue.charAt(integerpos);
            if (sectioninfo.thousandssep) { // see if this is a separator position
              fromend = integervalue.length - integerpos - 1;
              if (fromend > 2 && fromend % 3 == 0) {
                result += separatorchar;
              }
            }
            integerpos++;
          }
        }
        else if (op == scfn.commands.fraction_placeholder) { // add fraction part of number
          if (fractionpos >= fractionvalue.length) {
            if (operandstr == '0' || operandstr == '?') {
              result += operandstr == '0' ? '0' : ' ';
            }
          }
          else {
            result += fractionvalue.charAt(fractionpos);
          }
          fractionpos++;
        }

        else if (op == scfn.commands.decimal) { // decimal point
          if (negativevalue) {
            result += '-';
            negativevalue = 0;
          }
          result += decimalchar;
        }

        else if (op == scfn.commands.currency) { // currency symbol
          if (negativevalue) {
            result += '-';
            negativevalue = 0;
          }
          result += operandstr;
        }

        else if (op == scfn.commands.general) { // insert "General" conversion

          // *** Cut down number of significant digits to avoid floating point artifacts:

          if (value!=0) { // only if non-zero
            var factor = Math.floor(Math.LOG10E * Math.log(value)); // get integer magnitude as a power of 10
            factor = Math.pow(10, 13-factor); // turn into scaling factor
            value = Math.floor(factor * value + 0.5)/factor; // scale positive value, round, undo scaling
            if (!isFinite(value)) { return 'NaN'; }
          }
          if (negativevalue) {
            result += '-';
          }
          strvalue = value+''; // convert original value to string
          if (strvalue.indexOf('e')>=0) { // converted to scientific notation
          result += strvalue;
          continue;
        }
        strparts=strvalue.match(/^\+{0,1}(\d*)(?:\.(\d*)){0,1}$/); // get integer and fraction parts
        integervalue = strparts[1];
        if (!integervalue || integervalue=='0') { integervalue=''; }
        fractionvalue = strparts[2];
        if (!fractionvalue) { fractionvalue = ''; }
        integerpos = 0;
        fractionpos = 0;
        if (integervalue.length) {
          for (;integerpos < integervalue.length; integerpos++) {
            result += integervalue.charAt(integerpos);
            if (sectioninfo.thousandssep) { // see if this is a separator position
              fromend = integervalue.length - integerpos - 1;
              if (fromend > 2 && fromend % 3 == 0) {
                result += separatorchar;
              }
            }
          }
        }
        else {
          result += '0';
        }
        if (fractionvalue.length) {
          result += decimalchar;
          for (;fractionpos < fractionvalue.length; fractionpos++) {
            result += fractionvalue.charAt(fractionpos);
          }
        }
      }
      else if (op==scfn.commands.date) { // date placeholder
        operandstrlc = operandstr.toLowerCase();
        if (operandstrlc=='y' || operandstrlc=='yy') {
          result += (ymd.year+'').substring(2);
        }
        else if (operandstrlc=='yyyy') {
          result += ymd.year+'';
        }
        else if (operandstrlc=='d') {
          result += ymd.day+'';
        }
        else if (operandstrlc=='dd') {
          cval = 1000 + ymd.day;
          result += (cval+'').substr(2);
        }
        else if (operandstrlc=='ddd') {
          cval = Math.floor(rawvalue+6) % 7;
          result += DayNames3[cval];
        }
        else if (operandstrlc=='dddd') {
          cval = Math.floor(rawvalue+6) % 7;
          result += DayNames[cval];
        }
        else if (operandstrlc=='m') {
          result += ymd.month+'';
        }
        else if (operandstrlc=='mm') {
          cval = 1000 + ymd.month;
          result += (cval+'').substr(2);
        }
        else if (operandstrlc=='mmm') {
          result += MonthNames3[ymd.month-1];
        }
        else if (operandstrlc=='mmmm') {
          result += MonthNames[ymd.month-1];
        }
        else if (operandstrlc=='mmmmm') {
          result += MonthNames[ymd.month-1].charAt(0);
        }
        else if (operandstrlc=='h') {
          result += hrs+'';
        }
        else if (operandstrlc=='h]') {
          result += ehrs+'';
        }
        else if (operandstrlc=='mmin') {
          cval = (1000 + mins)+'';
          result += cval.substr(2);
        }
        else if (operandstrlc=='mm]') {
          if (emins < 100) {
            cval = (1000 + emins)+'';
            result += cval.substr(2);
          }
          else {
            result += emins+'';
          }
        }
        else if (operandstrlc=='min') {
          result += mins+'';
        }
        else if (operandstrlc=='m]') {
          result += emins+'';
        }
        else if (operandstrlc=='hh') {
          cval = (1000 + hrs)+'';
          result += cval.substr(2);
        }
        else if (operandstrlc=='s') {
          cval = Math.floor(secs);
          result += cval+'';
        }
        else if (operandstrlc=='ss') {
          cval = (1000 + Math.floor(secs))+'';
          result += cval.substr(2);
        }
        else if (operandstrlc=='am/pm' || operandstrlc=='a/p') {
          result += ampmstr;
        }
        else if (operandstrlc=='ss]') {
          if (esecs < 100) {
            cval = (1000 + Math.floor(esecs))+'';
            result += cval.substr(2);
          }
          else {
            cval = Math.floor(esecs);
            result += cval+'';
          }
        }
      }
      else if (op == scfn.commands.section) {
        // end of section
        break;
      }

      else if (op == scfn.commands.comparison) {
        // ignore
        continue;
      }

      else {
        result += '!! Parse error.!!';
      }
    }

    if (textcolor) {
      result = '<span style="color:'+textcolor+';">'+result+'</span>';
    }
    if (textstyle) {
      result = '<span style="'+textstyle+';">'+result+'</span>';
    }

    //console.log(result)

    return result;

    };

    /* *******************

    FormatNumber.parse_format_string(format_defs, format_string)

    Takes a format string (e.g., "#,##0.00_);(#,##0.00)") and fills in format_defs with the parsed info

    format_defs
    ["#,##0.0"]->{} - elements in the hash are one hash for each format
    .operators->[] - array of operators from parsing the format string (each a number)
    .operands->[] - array of corresponding operators (each usually a string)
    .sectioninfo->[] - one hash for each section of the format
    .start
    .integerdigits
    .fractiondigits
    .commas
    .percent
    .thousandssep
    .hasdates
    .hascomparison - true if any section has [<100], etc.

    ************************* */

    FormatNumber.parse_format_string = function(format_defs, format_string) {

      var scfn = FormatNumber;

      var format, section, sectioninfo;
      var integerpart = 1; // start out in integer part
      var lastwasinteger; // last char was an integer placeholder
      var lastwasslash; // last char was a backslash - escaping following character
      var lastwasasterisk; // repeat next char
      var lastwasunderscore; // last char was _ which picks up following char for width
      var inquote, quotestr; // processing a quoted string
      var inbracket, bracketstr, bracketdata; // processing a bracketed string
      var ingeneral, gpos; // checks for characters "General"
      var ampmstr, part; // checks for characters "A/P" and "AM/PM"
      var indate; // keeps track of date/time placeholders
      var chpos; // character position being looked at
      var ch; // character being looked at

      if (format_defs[format_string]) { return; } // already defined - nothing to do

      format = {operators: [], operands: [], sectioninfo: [{}]}; // create info structure for this format
      format_defs[format_string] = format; // add to other format definitions

      section = 0; // start with section 0
      sectioninfo = format.sectioninfo[section]; // get reference to info for current section
      sectioninfo.sectionstart = 0; // position in operands that starts this section
      sectioninfo.integerdigits = 0; // number of integer-part placeholders
      sectioninfo.fractiondigits = 0; // fraction placeholders
      sectioninfo.commas = 0; // commas encountered, to handle scaling
      sectioninfo.percent = 0; // times to scale by 100

      for (chpos=0; chpos<format_string.length; chpos++) { // parse
        ch = format_string.charAt(chpos); // get next char to examine
        if (inquote) {
          if (ch == '"') {
            inquote = 0;
            format.operators.push(scfn.commands.copy);
            format.operands.push(quotestr);
            continue;
          }
          quotestr += ch;
          continue;
        }
        if (inbracket) {
          if (ch==']') {
            inbracket = 0;
            bracketdata=FormatNumber.parse_format_bracket(bracketstr);
            if (bracketdata.operator==scfn.commands.separator) {
              sectioninfo.thousandssep = 1; // explicit [,]
              continue;
            }
            if (bracketdata.operator==scfn.commands.date) {
              sectioninfo.hasdate = 1;
            }
            if (bracketdata.operator==scfn.commands.comparison) {
              format.hascomparison = 1;
            }
            format.operators.push(bracketdata.operator);
            format.operands.push(bracketdata.operand);
            continue;
          }
          bracketstr += ch;
          continue;
        }

        if (lastwasslash) {
          format.operators.push(scfn.commands.copy);
          format.operands.push(ch);
          lastwasslash=false;
          continue;
        }

        if (lastwasasterisk) {
          format.operators.push(scfn.commands.copy);
          format.operands.push(ch+ch+ch+ch+ch); // do 5 of them since no real tabs
          lastwasasterisk=false;
          continue;
        }

        if (lastwasunderscore) {
          format.operators.push(scfn.commands.copy);
          format.operands.push(' ');
          lastwasunderscore=false;
          continue;
        }

        if (ingeneral) {
          if ('general'.charAt(ingeneral)==ch.toLowerCase()) {
            ingeneral++;
            if (ingeneral == 7) {
              format.operators.push(scfn.commands.general);
              format.operands.push(ch);
              ingeneral=0;
            }
            continue;
          }
          ingeneral = 0;
        }

        // last char was part of a date placeholder
        if (indate) {
          if (indate.charAt(0)==ch) { // another of the same char
            indate += ch; // accumulate it
            continue;
          }
          format.operators.push(scfn.commands.date); // something else, save date info
          format.operands.push(indate);
          sectioninfo.hasdate=1;
          indate = '';
        }
        if (ampmstr) {
          ampmstr += ch;
          part=ampmstr.toLowerCase();
          if (part!='am/pm'.substring(0,part.length) && part!='a/p'.substring(0,part.length)) {
            ampmstr='';
          }
          else if (part=='am/pm' || part=='a/p') {
            format.operators.push(scfn.commands.date);
            format.operands.push(ampmstr);
            ampmstr = '';
          }
          continue;
        }
        if (ch=='#' || ch=='0' || ch=='?') { // placeholder
        if (integerpart) {
          sectioninfo.integerdigits++;
          if (sectioninfo.commas) { // comma inside of integer placeholders
            sectioninfo.thousandssep = 1; // any number is thousands separator
            sectioninfo.commas = 0; // reset count of "thousand" factors
          }
          lastwasinteger = 1;
          format.operators.push(scfn.commands.integer_placeholder);
          format.operands.push(ch);
        }
        else {
          sectioninfo.fractiondigits++;
          format.operators.push(scfn.commands.fraction_placeholder);
          format.operands.push(ch);
        }
      } else if (ch == '.') {
        lastwasinteger = 0;
        format.operators.push(scfn.commands.decimal);
        format.operands.push(ch);
        integerpart = 0;
      } else if (ch === '$') {
        lastwasinteger = 0;
        format.operators.push(scfn.commands.currency);
        format.operands.push(ch);
      } else if (ch==',') {
        if (lastwasinteger) {
          sectioninfo.commas++;
        } else {
          format.operators.push(scfn.commands.copy);
          format.operands.push(ch);
        }
      } else if (ch=='%') {
        lastwasinteger = 0;
        sectioninfo.percent++;
        format.operators.push(scfn.commands.copy);
        format.operands.push(ch);
      } else if (ch=='"') {
        lastwasinteger = 0;
        inquote = 1;
        quotestr = '';
      } else if (ch=='[') {
        lastwasinteger = 0;
        inbracket = 1;
        bracketstr = '';
      } else if (ch=='\\') {
        lastwasslash = 1;
        lastwasinteger = 0;
      } else if (ch=='*') {
        lastwasasterisk = 1;
        lastwasinteger = 0;
      } else if (ch=='_') {
        lastwasunderscore = 1;
        lastwasinteger = 0;
      } else if (ch==';') {
        section++; // start next section
        format.sectioninfo[section] = {}; // create a new section
        sectioninfo = format.sectioninfo[section]; // get reference to info for current section
        sectioninfo.sectionstart = 1 + format.operators.length; // remember where it starts
        sectioninfo.integerdigits = 0; // number of integer-part placeholders
        sectioninfo.fractiondigits = 0; // fraction placeholders
        sectioninfo.commas = 0; // commas encountered, to handle scaling
        sectioninfo.percent = 0; // times to scale by 100
        integerpart = 1; // reset for new section
        lastwasinteger = 0;
        format.operators.push(scfn.commands.section);
        format.operands.push(ch);
      } else if (ch.toLowerCase()=='g') {
        ingeneral = 1;
        lastwasinteger = 0;
      } else if (ch.toLowerCase()=='a') {
        ampmstr = ch;
        lastwasinteger = 0;
      } else if ('dmyhHs'.indexOf(ch)>=0) {
        indate = ch;
      } else {
        lastwasinteger = 0;
        format.operators.push(scfn.commands.copy);
        format.operands.push(ch);
      }
    }

    // last char was part of unsaved date placeholder
    if (indate) {
      format.operators.push(scfn.commands.date);
      format.operands.push(indate);
      sectioninfo.hasdate = 1;
    }

    return;

    }


    /* *******************

    bracketdata = FormatNumber.parse_format_bracket(bracketstr)

    Takes a bracket contents (e.g., "RED", ">10") and returns an operator and operand

    bracketdata->{}
    .operator
    .operand

    ************************* */

    FormatNumber.parse_format_bracket = function(bracketstr) {

      var scfn = FormatNumber;

      var bracketdata={};
      var parts;

      // currency
      if (bracketstr.charAt(0)=='$') {
        bracketdata.operator = scfn.commands.currency;
        parts=bracketstr.match(/^\$(.+?)(\-.+?){0,1}$/);
        if (parts) {
          bracketdata.operand = parts[1] || DefaultCurrency || '$';
        } else {
          bracketdata.operand = bracketstr.substring(1) || DefaultCurrency || '$';
        }
      } else if (bracketstr=='?$') {
        bracketdata.operator = scfn.commands.currency;
        bracketdata.operand = '[?$]';
      } else if (AllowedColors[bracketstr.toUpperCase()]) {
        bracketdata.operator = scfn.commands.color;
        bracketdata.operand = AllowedColors[bracketstr.toUpperCase()];
      } else if (parts=bracketstr.match(/^style=([^']*)$/)) {
        // [style=...]
        bracketdata.operator = scfn.commands.style;
        bracketdata.operand = parts[1];
      }
      else if (bracketstr==',') {
        bracketdata.operator = scfn.commands.separator;
        bracketdata.operand = bracketstr;
      }
      else if (AllowedDates[bracketstr.toUpperCase()]) {
        bracketdata.operator = scfn.commands.date;
        bracketdata.operand = AllowedDates[bracketstr.toUpperCase()];
      }
      else if (parts=bracketstr.match(/^[<>=]/)) { // comparison operator
        parts=bracketstr.match(/^([<>=]+)(.+)$/); // split operator and value
        bracketdata.operator = scfn.commands.comparison;
        bracketdata.operand = parts[1]+':'+parts[2];
      }
      else { // unknown bracket
        bracketdata.operator = scfn.commands.copy;
        bracketdata.operand = '['+bracketstr+']';
      }

      return bracketdata;

    }

    function text(value, format, currency_char) {
      return FormatNumber.formatNumberWithFormat(value, format, currency_char);
    }

    // TRIMS returns a string without whitespace at the beginning or end.
    function trim(text) {
        if (typeof text !== 'string') {
            return error$2.value;
        }
        return text.trim();
    }

    // Copyright 2015 JC Fisher

    // UPPER converts a string to upper case
    function upper(string) {
      return string.toUpperCase()
    }

    // Find a needle in a table searching horizontally.
    function hlookup(needle, table, index, exactmatch) {
        if ( index === void 0 ) index=1;

        if (typeof needle === "undefined" || isblank(needle)) {
            return null;
        }

        if (index > table.length) {
          return error$2.ref
        }

        var needleLower = (needle + '').toLowerCase(),
        row = table[0];

        for (var i = 0; i < row.length; i++){

          if ((exactmatch && row[i]===needle) ||
              ((row[i] == needle) ||
               (typeof row[i] === "string" && row[i].toLowerCase().indexOf(needleLower) != -1) )) {
                return table[index-1][i];
            }
        }

        return error$2.na;
    }

    // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes?v=example
    function includes(searchElement, searchList, fromIndex) {

      // 1. Let O be ? ToObject(this value).
      if (searchList == null) {
        throw new TypeError('"searchList" is null or not defined');
      }

      var o = Object(searchList);

      // 2. Let len be ? ToLength(? Get(O, "length")).
      var len = o.length >>> 0;

      // 3. If len is 0, return false.
      if (len === 0) {
        return false;
      }

      // 4. Let n be ? ToInteger(fromIndex).
      //    (If fromIndex is undefined, this step produces the value 0.)
      var n = fromIndex | 0;

      // 5. If n ≥ 0, then
      //  a. Let k be n.
      // 6. Else n < 0,
      //  a. Let k be len + n.
      //  b. If k < 0, let k be 0.
      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

      function sameValueZero(x, y) {
        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
      }

      // 7. Repeat, while k < len
      while (k < len) {
        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
        // b. If SameValueZero(searchElement, elementK) is true, return true.
        // c. Increase k by 1.
        if (sameValueZero(o[k], searchElement)) {
          return true;
        }
        k++;
      }

      // 8. Return false
      return false;
    }

    // notincludes returns true when the searchElement is not found in the searchList.
    function notincludes(searchElement, searchList, fromIndex) {
      return !includes(searchElement, searchList, fromIndex);
    }

    // index returns the value in a row and column from a 2d array
    function index(reference, row_num, column_num) {
      if ( column_num === void 0 ) column_num=1;

      var row;

      if (!isarray(reference) || isblank(row_num)) {
        return error$2.value
      }

      if (reference.length < row_num) {
        return error$2.ref
      }

      row = reference[row_num-1];

      if (!isarray(row)) {
        return error$2.value
      }

      if (row.length < column_num) {
        return error$2.ref
      }

      return row[column_num-1];
    }

    // Copyright 2015 JC Fisher

    // LOOKUP find a value in an array.
    function lookup() {
        var lookup_value, lookup_array, lookup_vector, results_vector;
        if (arguments.length === 2) { // array form
            var wide = false;

            lookup_value = arguments[0].valueOf();
            lookup_array = arguments[1];

            for (var i = 0; i < lookup_array.length; i++) {
                if (typeof lookup_array[i] !== 'undefined' && lookup_value === lookup_array[i].valueOf()) {
                    return lookup_array[i];
                }
            }

        } else if (arguments.length === 3) { // vector form`
            lookup_value = arguments[0].valueOf();
            lookup_vector = arguments[1];
            results_vector = arguments[2];

            for (var i = 0; i < lookup_vector.length; i++) {
                if (typeof lookup_vector[i] !== 'undefined' && lookup_value === lookup_vector[i].valueOf()) {
                    return results_vector[i];
                }
            }

        }

        return error.na;

    }

    // MATCH returns an index in `array_reference` by searching for `lookup_reference`.
    function match(lookup_reference, array_reference, matchType) {

      var lookupArray, lookupValue, index, indexValue;

      // Gotta have only 2 arguments folks!
      if (arguments.length === 2) {
        matchType = 1;
      }

      // Find the lookup value inside a worksheet cell, if needed.
      lookupValue = lookup_reference;


      // Find the array inside a worksheet range, if needed.
      if (isarray(array_reference)) {
        lookupArray = array_reference;
      } else {
        return error$2.na;
      }

      // Gotta have both lookup value and array
      if (!lookupValue && !lookupArray) {
        return error$2.na;
      }

      // Bail on weird match types!
      if (matchType !== -1 && matchType !== 0 && matchType !== 1) {
        return error$2.na;
      }

      for (var idx = 0; idx < lookupArray.length; idx++) {
        if (matchType === 1) {
          if (lookupArray[idx] === lookupValue) {
            return idx + 1;
          } else if (lookupArray[idx] < lookupValue) {
            if (!indexValue) {
              index = idx + 1;
              indexValue = lookupArray[idx];
            } else if (lookupArray[idx] > indexValue) {
              index = idx + 1;
              indexValue = lookupArray[idx];
            }
          }
        } else if (matchType === 0) {
          if (typeof lookupValue === 'string') {
            // '?' is mapped to the regex '.'
            // '*' is mapped to the regex '.*'
            // '~' is mapped to the regex '\?'
            if (idx === 0) {
              lookupValue = "^" + lookupValue.replace(/\?/g, '.').replace(/\*/g, '.*').replace(/~/g, '\\?') + "$";
            }
            if (typeof lookupArray[idx] !== "undefined") {
              if (String(lookupArray[idx]).toLowerCase().match(String(lookupValue).toLowerCase())) {
                return idx + 1;
              }
            }
          } else {
            if (typeof lookupArray[idx] !== "undefined" && lookupArray[idx] !== null && lookupArray[idx].valueOf() === lookupValue) {
              return idx + 1;
            }
          }
        } else if (matchType === -1) {
          if (lookupArray[idx] === lookupValue) {
            return idx + 1;
          } else if (lookupArray[idx] > lookupValue) {
            if (!indexValue) {
              index = idx + 1;
              indexValue = lookupArray[idx];
            } else if (lookupArray[idx] < indexValue) {
              index = idx + 1;
              indexValue = lookupArray[idx];
            }
          }
        }
      }

      return index ? index : error$2.na;

    };

    // VLOOKUP find a needle in a table searching vertically.
    function vlookup(needle, table, index, exactmatch) {
        if ( table === void 0 ) table=[];
        if ( index === void 0 ) index=1;
        if ( exactmatch === void 0 ) exactmatch=false;


        if ( iserror(needle) || isblank(needle) ) {
            return needle;
        }

        for (var i = 0; i < table.length; i++){
            var row = table[i];

            if (index > row.length){
              return error$2.ref
            }

            if ((exactmatch && row[0]===needle) ||
                ((row[0] == needle) ||
                 (typeof row[0] === "string" && row[0].toLowerCase().indexOf(needle.toLowerCase()) != -1) )) {
                return (index < (row.length+1) ? row[index-1] : row[0]);
            }
        }

        return error$2.na;

    }

    // date returns a serial number given a year, month and day.
    function date(year, month, day) {
      return serial(new Date(year, month-1, day));
    }

    // DATEVALUE parses a date string and returns a serial number.
    function datevalue(d) {
      return serial(parsedate(d));
    }

    // DATEDIF return the difference between two dates given a start date, end date and unit.
    function datedif(start_date, end_date, unit) {
      var second=1000, minute=second*60, hour=minute*60, day=hour*24, week=day*7;
      start_date = parsedate(start_date),
      end_date = parsedate(end_date)

      var timediff = end_date - start_date;
      if (isnan(timediff)) { return NaN; }

      switch (unit) {
        case "Y": return end_date.getFullYear() - start_date.getFullYear();
        case "M": return (
          ( end_date.getFullYear() * 12 + end_date.getMonth() )
            -
          ( start_date.getFullYear() * 12 + start_date.getMonth() )
        );
        case "W"  : return Math.floor(timediff / week);
        case "D"   : return Math.floor(timediff / day);
        case "MD"   : return end_date.getdate() - start_date.getdate();
        case "YM" : return end_date.getMonth() - start_date.getMonth();
        case "YD": return new error("NOT IMPLEMENTED");
        default: return undefined;
      }

    }

    // DAY parses a date string and returns the day of the month.
    function day(d) {
      return parsedate(d).getDate()
    }

    function days360(start_date, end_date, method) {
        method = parsebool(method);
        start_date = parsedate(start_date);
        end_date = parsedate(end_date);

        if (start_date instanceof Error) {
            return start_date;
        }
        if (end_date instanceof Error) {
            return end_date;
        }
        if (method instanceof Error) {
            return method;
        }
        var sm = start_date.getMonth();
        var em = end_date.getMonth();
        var sd, ed;
        if (method) {
            sd = start_date.getDate() === 31 ? 30 : start_date.getDate();
            ed = end_date.getDate() === 31 ? 30 : end_date.getDate();
        } else {
            var smd = new Date(start_date.getFullYear(), sm + 1, 0).getDate();
            var emd = new Date(end_date.getFullYear(), em + 1, 0).getDate();
            sd = start_date.getDate() === smd ? 30 : start_date.getDate();
            if (end_date.getDate() === emd) {
                if (sd < 30) {
                    em++;
                    ed = 1;
                } else {
                    ed = 30;
                }
            } else {
                ed = end_date.getDate();
            }
        }
        return (
          360 * (end_date.getFullYear() - start_date.getFullYear()) + 30 * (em - sm) + (ed - sd)
        );
    }

    function edate(start_date, months) {
        start_date = parsedate(start_date);

        if (start_date instanceof Error) {
            return start_date;
        }
        if (isNaN(months)) {
            return error.value;
        }
        months = parseInt(months, 10);
        start_date.setMonth(start_date.getMonth() + months);
        return serial(start_date);
    };

    function eomonth(start_date, months) {
      start_date = parsedate(start_date);

      if (start_date instanceof Error) {
        return start_date;
      }
      if (isNaN(months)) {
        return error$2.value;
      }
      months = parseInt(months, 10);
      return new Date(start_date.getFullYear(), start_date.getMonth() + months + 1, 0);
    }

    function hour(value) {
      if (isdate(value)) {
        return value.getHours();
      }

      // remove numbers before decimal place and convert fraction to 24 hour scale.
      return trunc((value - trunc(value)) * 24);
    }

    function minute(value) {
      if (isdate(value)) {
        return value.getMinutes();
      }

      // calculate total seconds
      var totalSeconds = (value - trunc(value)) * SecondsInDay;
      // calculate number of seconds for hour components
      var hourSeconds = trunc(totalSeconds / SecondsInHour) * SecondsInHour;
      // calculate the number seconds after remove seconds from the hours and convert to minutes
      return trunc((totalSeconds - hourSeconds) / SecondsInMinute);
    }

    // MONTH parses a date value and returns the month of the year.
    function month(d) {
      return parsedate(d).getMonth() + 1
    }

    function timevalue(time_text) {
        // The JavaScript new Date() does not accept only time.
        // To workaround the issue we put 1/1/1900 at the front.

        var date = new Date("1/1/1900 " + time_text);

        if (date instanceof Error) {
            return date;
        }

        return (SecondsInHour * date.getHours() +
                SecondsInMinute * date.getMinutes() +
                date.getSeconds()) / SecondsInDay;
    };

    function now() {
      var d = new Date();
      return datevalue(d.toLocaleDateString()) + timevalue(d.toLocaleTimeString());
    };

    function second(value) {
      if (isdate(value)) {
        return value.getSeconds();
      }

      // calculate total seconds
      var totalSeconds = (value - trunc(value)) * SecondsInDay;

      // calculate number of seconds for hour component
      var hourSeconds = trunc(totalSeconds / SecondsInHour) * SecondsInHour;

      // calculate number of seconds in minute component
      var minuteSeconds =
        trunc((totalSeconds - hourSeconds) / SecondsInMinute) * SecondsInMinute;

      // remove seconds for hours and minutes and round to nearest value
      return Math.round(totalSeconds - hourSeconds - minuteSeconds);
    }

    function today() {
      var d = new Date();
      return datevalue(d.toLocaleDateString())
    };

    function time(hour, minute, second) {
      return +((hour*3600 + minute*60 + second) / SecondsInDay).toFixed(15);
    }

    // YEAR parses a date value and returns the year of the year.
    function year(d) {
      return parsedate(d).getFullYear()
    }

    function yearfrac(start_date, end_date, basis) {
      start_date = parsedate(start_date);
      if (start_date instanceof Error) {
        return start_date;
      }
      end_date = parsedate(end_date);
      if (end_date instanceof Error) {
        return end_date;
      }

      basis = basis || 0;
      var sd = start_date.getDate();
      var sm = start_date.getMonth() + 1;
      var sy = start_date.getFullYear();
      var ed = end_date.getDate();
      var em = end_date.getMonth() + 1;
      var ey = end_date.getFullYear();

      function isLeapYear(year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }
      function daysBetween(a, b) { return serial(b) - serial(a) }

      switch (basis) {
        case 0:
        // US (NASD) 30/360
        if (sd === 31 && ed === 31) {
          sd = 30;
          ed = 30;
        } else if (sd === 31) {
          sd = 30;
        } else if (sd === 30 && ed === 31) {
          ed = 30;
        }
        return ((ed + em * 30 + ey * 360) - (sd + sm * 30 + sy * 360)) / 360;
        case 1:
        // Actual/actual
        var feb29Between = function(date1, date2) {
          var year1 = date1.getFullYear();
          var mar1year1 = new Date(year1, 2, 1);
          if (isLeapYear(year1) && date1 < mar1year1 && date2 >= mar1year1) {
            return true;
          }
          var year2 = date2.getFullYear();
          var mar1year2 = new Date(year2, 2, 1);
          return (isLeapYear(year2) && date2 >= mar1year2 && date1 < mar1year2);
        };
        var ylength = 365;
        if (sy === ey || ((sy + 1) === ey) && ((sm > em) || ((sm === em) && (sd >= ed)))) {
          if ((sy === ey && isLeapYear(sy)) ||
          feb29Between(start_date, end_date) ||
          (em === 1 && ed === 29)) {
            ylength = 366;
          }
          return daysBetween(start_date, end_date) / ylength;
        }
        var years = (ey - sy) + 1;
        var days = (new Date(ey + 1, 0, 1) - new Date(sy, 0, 1)) / 1000 / 60 / 60 / 24;
        var average = days / years;
        return daysBetween(start_date, end_date) / average;
        case 2:
        // Actual/360
        return daysBetween(start_date, end_date) / 360;
        case 3:
        // Actual/365
        return daysBetween(start_date, end_date) / 365;
        case 4:
        // European 30/360
        return ((ed + em * 30 + ey * 360) - (sd + sm * 30 + sy * 360)) / 360;
      }
    };

    // SUM a given list of `numbers`
    function sum() {
        var numbers = [], len = arguments.length;
        while ( len-- ) numbers[ len ] = arguments[ len ];

        return reduce(flatten(flatten(numbers)), function (a, b) {
          if (typeof b !== 'number') { return error$2.value }
          return a + b
        },0);
    }

    // AVERAGE computes sum of items divided by number of items
    function average() {
      var items = [], len = arguments.length;
      while ( len-- ) items[ len ] = arguments[ len ];


      // compute sum all of the items.
      var v = sum.apply(void 0, items)

      // return sum when computed error.
      if (iserror(v)) {
        return v;
      }

      // return sum divided by item count
      return  v / items.length;
    }

    // MIN returns the smallest number from a `list`.
    function min() {
      var list = [], len = arguments.length;
      while ( len-- ) list[ len ] = arguments[ len ];


      var values = flatten( list )
      if (values.length === 0) { return; }
      return reduce( values, function (min, next) {
        if (isblank(min)) { return next; }
        else if (isnumber(next)) { return Math.min(min, next); }
        else { return min; }
      });
    }

    // MAX returns the largest number from a `list`.
    function max() {
      var list = [], len = arguments.length;
      while ( len-- ) list[ len ] = arguments[ len ];


      var values = flatten( list )
      if (values.length === 0) { return; }
      return reduce( values, function (max, next) {
        if (isblank(max)) { return next; }
        else if (isnumber(next)) { return Math.max(max, next); }
        else { return max; }
      });
    }

    // FILTER limits a range based on arrays of boolean values.
    function filter(range) {
      var filters = [], len = arguments.length - 1;
      while ( len-- > 0 ) filters[ len ] = arguments[ len + 1 ];


      // A filter is an array of true/false values.
      // The filter may be for rows or for columns but not both.
      // A array filter may only filter a range that covers a single row or a single column.

      function makefilter() {
        return function(value, index) {
          return reduce( filters, function( previousValue, currentValue ) {
            if (previousValue === false ) {
              return false;
            } else {
              return branch(
                isarray(currentValue),
                function () { return currentValue[index]; },
                isfunction(currentValue),
                function () { return currentValue( value, index ); },
                istext(currentValue),
                function () { return run(currentValue, value); },
                error$2.na
              )
            }
          }, true);
        }
      }

      return range.filter( makefilter() )

    }

    // Functions for each operator.
    var filterTypes = {

      $noop: function () { return function () { return false; }; },
      $eq: function (queryVal) { return function (row, field) { return eq(row[field], queryVal); }; },
      $ne: function (queryVal) { return function (row, field) { return ne(row[field], queryVal); }; },
      $gt: function (queryVal) { return function (row, field) { return gt(row[field], queryVal); }; },
      $gte: function (queryVal) { return function (row, field) { return gte(row[field], queryVal); }; },
      $lt: function (queryVal) { return function (row, field) { return lt(row[field], queryVal); }; },
      $lte: function (queryVal) { return function (row, field) { return lte(row[field], queryVal); }; },
      $in: function (queryVal) { return function (row, field) { return isarray(queryVal) && includes(row[field], queryVal); }; },
      $nin: function (queryVal) { return function (row, field) { return isarray(queryVal) && !includes(row[field], queryVal); }; },
      $text: function (queryVal) { return function (row, field) { return search(queryVal, row[field]) > 0; }; },
      $exists: function (queryVal) { return function (row, field) { return istruthy(queryVal) ? row.hasOwnProperty(field) : !row.hasOwnProperty(field); }; },

      $and: function (queryVal) { return function (row, field) { return true; }; },
      $or: function (queryVal) { return function (row, field) { return true; }; }

    }

    // Run the filter against the data with the settings.
    function query(data, query) {

      var comparison = function (field, op, value) { return function (row) { return (filterTypes[op] || filterTypes['$noop'])(value)(row, field); }; }

      var comparator = function (list, key) { return function (row) { return branch(
        isobject(list[key]),
        function () { return and.apply(
          void 0, map( keys(list[key]), function (d) {
            return comparison( key, d, list[key][d] )(row)
          })
        ); },
        function () { return comparison( key, '$eq', list[key])(row); }
      ); }; }

      var comparisonGroup = function (row, list, key, op) {
        if ( op === void 0 ) op=and;


        if (!isarray(list[key])) {
          throw new Error(("$" + (op.name) + " expects array!"));
        }

        return op.apply(
          void 0, map(
            list[key],
            function (d) { return op.apply( void 0, map( keys(d), function (e) { return comparator(d, e)(row); } )); }
          )
        )

      }

      var composeQuery = function (list) { return reduce(
        keys(list),
        function (funcs, key) { return funcs.concat(
          function (row) { return branch(
              key === '$and',
              function () { return comparisonGroup(row, list, key, and); },
              key === '$or',
              function () { return comparisonGroup(row, list, key, or); },
              function () { return comparator(list, key)(row); }
            ); }
        ); },
        []
      ); };

      // Compose a list of functions to filter each field.
      var funcs = composeQuery(query);

      // Execute the filter on the data.
      return filter.apply(
        void 0, [ data ].concat( map( funcs, function (filter) { return map( data, function (row) { return filter(row); } ); } ) )
      )
    }

    // Returns the accrued interest for a security that pays periodic interest.
    function accrint(issue, first, settlement, rate, par, frequency, basis) {
      if ( basis === void 0 ) basis=0;


      // Return error if either date is invalid
      var issueDate      = parsedate(issue);
      var firstDate      = parsedate(first);
      var settlementDate = parsedate(settlement);

      if (!isdate(issueDate) || !isdate(firstDate) || !isdate(settlementDate) || !isnumber(par)) {
        return error$2.value;
      }

      // Return error if either rate or par are lower than or equal to zero
      if (rate <= 0 || par <= 0) {
        return error$2.num;
      }

      // Return error if frequency is neither 1, 2, or 4
      if ([1, 2, 4].indexOf(frequency) === -1) {
        return error$2.num;
      }

      // Return error if basis is neither 0, 1, 2, 3, or 4
      if ([0, 1, 2, 3, 4].indexOf(basis) === -1) {
        return error$2.num;
      }

      // Return error if settlement is before or equal to issue
      if (settlementDate <= issueDate) {
        return error$2.num;
      }

      // Compute accrued interest
      return par * rate * yearfrac(issue, settlement, basis);

    };

    function fv(rate, periods, payment, value, type) {
      if ( value === void 0 ) value=0;
      if ( type === void 0 ) type=0;


      // is this error code correct?
      if (isblank(rate)) { return error$2.na }
      if (isblank(periods)) { return error$2.na }
      if (isblank(payment)) { return error$2.na }

      var fv;
      if (rate === 0) {
        fv = value + payment * periods;
      } else {
        var term = Math.pow(1 + rate, periods);
        if (type === 1) {
          fv = value * term + payment * (1 + rate) * (term - 1) / rate;
        } else {
          fv = value * term + payment * (term - 1) / rate;
        }
      }
      return -fv;
    };

    function nper(rate, pmt, pv, fv, type) {
      var log,
      result;
      rate = parseFloat(rate || 0);
      pmt = parseFloat(pmt || 0);
      pv = parseFloat(pv || 0);
      fv = (fv || 0);
      type = (type || 0);

      log = function(prim) {
        if (isnan(prim)) {
          return Math.log(0);
        }
        var num = Math.log(prim);
        return num;
      }

      if (rate == 0.0) {
        result = (-(pv + fv)/pmt);
      } else if (type > 0.0) {
        result = (log(-(rate*fv-pmt*(1.0+rate))/(rate*pv+pmt*(1.0+rate)))/(log(1.0+rate)));
      } else {
        result = (log(-(rate*fv-pmt)/(rate*pv+pmt))/(log(1.0+rate)));
      }

      if (isnan(result)) {
        result = 0;
      }

      return result;
    }

    // Copyright 2015 JC Fisher

    function npv(rate) {
        var values = [], len = arguments.length - 1;
        while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];

        rate = rate * 1;
        var factor = 1,
            sum = 0;

        for(var i = 0; i < values.length; i++) {
            var factor = factor * (1 + rate);
            sum += values[i] / factor;
        }

        return sum;
    }

    // PMT returns a loan payment
    function pmt(rate, periods, present, future, type) {
      if ( future === void 0 ) future = 0;
      if ( type === void 0 ) type = 0;


      if (!isnumber(rate) || !isnumber(periods)) {
        return error$2.value;
      }

      if (rate === 0) {
        return -((present + future) / periods);
      } else {
        var term = Math.pow(1 + rate, periods);
        if (type === 1) {
          return -((future * rate / (term - 1) + present * rate / (1 - 1 / term)) / (1 + rate));
        } else {
          return -(future * rate / (term - 1) + present * rate / (1 - 1 / term));
        }
      }

    };

    function cumipmt(rate, periods, value, start, end, type) {
      // Credits: algorithm inspired by Apache OpenOffice
      // Credits: Hannes Stiebitzhofer for the translations of function and variable names
      rate = numbervalue(rate);
      periods = numbervalue(periods);
      value = numbervalue(value);

      // check if any inputs are errors.
      if (iserror(rate) || iserror(periods) || iserror(value)) {
        return _error.value;
      }

      // Return error if either rate, periods, or value are lower than or equal to zero
      if (rate <= 0 || periods <= 0 || value <= 0) {
        return _error.num;
      }

      // Return error if start < 1, end < 1, or start > end
      if (start < 1 || end < 1 || start > end) {
        return _error.num;
      }

      // Return error if type is neither 0 nor 1
      if (type !== 0 && type !== 1) {
        return _error.num;
      }

      // Compute cumulative interest
      var payment = pmt(rate, periods, value, 0, type);
      var interest = 0;

      if (start === 1) {
        if (type === 0) {
          interest = -value;
          start++;
        }
      }

      for (var i = start; i <= end; i++) {
        if (type === 1) {
          interest += fv(rate, i - 2, payment, value, 1) - payment;
        } else {
          interest += fv(rate, i - 1, payment, value, 0);
        }
      }
      interest *= rate;

      // Return cumulative interest
      return interest;
    }

    function ipmt(rate, period, periods, present, future, type) {
      if ( future === void 0 ) future = 0;
      if ( type === void 0 ) type = 0;

      // parse numbers from input.
      rate = numbervalue(rate);
      period = numbervalue(period);
      periods = numbervalue(periods);
      present = numbervalue(present);
      future = numbervalue(future);
      type = numbervalue(type);

      if (iserror(rate, period, periods, present, future, type)) {
        return _error.value;
      }

      // Compute payment
      var payment = pmt(rate, periods, present, future, type);

      // Compute interest
      var interest;
      if (period === 1) {
        if (type === 1) {
          interest = 0;
        } else {
          interest = -present;
        }
      } else {
        if (type === 1) {
          interest = fv(rate, period - 2, payment, present, 1) - payment;
        } else {
          interest = fv(rate, period - 1, payment, present, 0);
        }
      }

      // Return interest
      return interest * rate;
    }

    function pv(rate, periods, payment, future, type) {
      if ( future === void 0 ) future=0;
      if ( type === void 0 ) type=0;


      // is this error code correct?
      if (isblank(rate)) { return error$2.na }
      if (isblank(periods)) { return error$2.na }
      if (isblank(payment)) { return error$2.na }

      if (rate === 0) {
        return -payment * periods - future;
      } else {
        return (((1 - Math.pow(1 + rate, periods)) / rate) * payment * (1 + rate * type) - future) / Math.pow(1 + rate, periods);
      }
    };

    // BIN2DEC converts binary string into decimal value
    function bin2dec(value) {
        var valueAsString;

        if (typeof value === "string") {
            valueAsString = value;
        } else if (typeof value !== "undefined") {
            valueAsString = value.toString();
        } else {
            return error$2.NA;
        }

        if (valueAsString.length > 10) { return error$2.NUM; }

        // we subtract 512 when the leading number is 0.
        if (valueAsString.length === 10 && valueAsString[0] === '1') {
            return parseInt(valueAsString.substring(1), 2) - 512;
        }

        // Convert binary number to decimal with built-in facility
        return parseInt(valueAsString, 2);

    };

    // based on https://github.com/sutoiku/formula.js/blob/mast../src/engineering.js
    function dec2bin(input, places) {

      // exit if input is an error
      if (input instanceof Error) {
        return number;
      }

      // cast input to number
      var number = parseInt(input);

      if (!/^-?[0-9]{1,3}$/.test(number) || isnan(number)) {
        return error$2.value;
      }

      // Return error.if number is not decimal, is lower than -512, or is greater than 511
      if (number < -512 || number > 511) {
        return error$2.num;
      }

      // Ignore places and return a 10-character binary number if number is negative
      if (number < 0) {
        return '1' + rept('0', 9 - (512 + number).toString(2).length) + (512 + number).toString(2);
      }

      // Convert decimal number to binary
      var result = parseInt(number, 10).toString(2);

      // Return binary number using the minimum number of characters necessary if places is undefined
      if (typeof places === 'undefined') {
        return result;
      } else {
        // Return error.if places is nonnumeric
        if (!/^-?[0-9]{1,3}$/.test(places) || isnan(places)) {
          return error$2.value;
        }

        // Return error.if places is negative
        if (places < 0) {
          return error$2.num;
        }

        // Truncate places in case it is not an integer
        places = Math.floor(places);

        // Pad return value with leading 0s (zeros) if necessary (using Underscore.string)
        return (places >= result.length) ? rept('0', places - result.length) + result : error$2.num;
      }
    }

    // OCT2DEC converts a octal value into a decimal value.
    function oct2dec(octalNumber) {
      // Credits: Based on implementation found in https://gist.github.com/ghalimi/4525876#file-oct2dec-js
      // Return error.when number passed in is not octal or has more than 10 digits
      if (!/^[0-7]{1,10}$/.test(octalNumber)) { return error$2.num; }

      // Convert octal number to decimal number
      var nonNegativeDecimalNumber = parseInt(octalNumber, 8);

      // Returns the corresponding decimal number
      // Two's Complement Decimal Range: -(2^N-1) to (2^N-1 - 1) where N=30 (N = number of bits) and ^ means raised to the power of
      // 2^N-1 = 2^(30 - 1) = 2^29 = 536870912
      // 2^N-1 - 1 = 536870912 - 1 = 536870911
      // 2^N = 2^30 = 1073741824
      // Two's Complement Decimal Range: [-536870912,536870911]
      // Largest octal number allowed: 7777777777 which in decimal is 1073741823 = 2^N - 1
      // Case 1: Negative Range
      //  if nonNegativeDecimalNumber >= 2^N-1, then return (nonNegativeNumber - 2^N)
      //  Smallest Number: 2^N-1 - 2^N = 2^N-1 - 2*2^N-1 = 2^N-1 * (1 - 2) = 2^N-1 * (-1) = -2^N-1
      //  Largest Number: (2^N - 1) - (2^N) = (2^N - 2^N) - 1 = -1
      //  Range: [-2^N-1, -1] = [-536870912, -1]
      //
      // Smallest octal number allowed: 0 which in decimal is 0
      // Case 2: Non-Negative Range
      //   Range: [0, 2^N-1 - 1] = [0, 536870911]

      return (nonNegativeDecimalNumber >= 536870912) ? nonNegativeDecimalNumber - 1073741824 : nonNegativeDecimalNumber;
    }

    // pluck a property from a list of objects.
    function pluck(prop, list) {

      // Ensure that the list is an array.
      if (!isarray(list)) {
        return error$2.na
      }

      // Map the list to the property.
      return map( list, function (d) { return d[prop]; } )
    }

    // SORT an array of objects.
    //
    // sort(reference(reference: Array, ...criteria : List<string>)
    //
    // The list<string> will also be reduced into a single function which
    // interprets the strings as pairs. The odd items are fields and the
    // even ones are direction (ASC|DESC).
    function sort(ref) {
      var criteria = [], len = arguments.length - 1;
      while ( len-- > 0 ) criteria[ len ] = arguments[ len + 1 ];


      // reduce the criteria array into a function
      var makeComparer = function () {
        return function(a, b) {
          var result = 0;
          for (var i = 0; i < criteria.length; i=i+2) {
            if (result !== 0) { continue; }

            var field = (typeof criteria[i] === 'string' ? criteria[i] : criteria[i] - 1),
                order = criteria[i+1];

            if (a[field] < b[field]) {
              result = order ? -1 : 1;
            }

            if (a[field] > b[field]) {
              result = order ? 1 : -1;
            }

          }

          return result;

        }

      }

      if (!isarray(ref)) {
        return error$2.na;
      }

      return ref.sort( makeComparer() );

    }

    // convert array into nested array.
    // example: unflatten([1,2,3,4]) -> [[1,2], [3, 4]]
    function unflatten(ref, len){
      if ( len === void 0 ) len=2;


      // if the reference data is not an array then stop.
      if (!isarray(ref)) {
        return error$2.value;
      }


      // use a reduction algorithm to convert data.
      return reduce( ref, function(p, v, i) {

        // The first iteration and every nth iteration.
        if ( i === 0 || i % len === 0) {
          p = p.concat([[]])
        }

        // determine last index in collected value (e.g. p).
        var lastIndex = p.length-1;

        // add the value to the last available spot.
        p[lastIndex] = p[lastIndex].concat(v)


        // return the new collected value.
        return p;
      }, []);
    }

    // CHANGED computes the list of keys that are different between two objects.
    function changed(a, b) {

      // Compute the keys in object a and b.
      var keysA = keys(a),
      keysB = keys(b)

      // Find the unique set of properties comparing a to b and b to a.
      return unique(
        keysA
        .filter( function (n) { return a[n] !== b[n]; })
        .concat(
          keysB
          .filter( function (n) { return a[n] !== b[n]; })
        )
      )
    }

    function diff(a, b) {
      var keysA = keys(a),
      keysB = keys(b),
      InA = keysB.filter(function (n) { return keysA.indexOf(n) > -1; }),
      NotInA = keysB.filter(function (n) { return keysA.indexOf(n) === -1; }),
      NotInB = keysA.filter(function (n) { return keysB.indexOf(n) === -1; }),
      Diff = InA.filter( function (n) { return a[n] !== b[n]; })

      return {
        unique_left: NotInA,
        unique_right: NotInB,
        diff: reduce( Diff, function (x, y) {
          var diff = { }
          diff[y] = { left: a[y], right: b[y]}
          return assign({}, x, diff)
        }, {})
      }
    }

    // SELECT fields from object
    function select(fields, body) {
      // non-json
      if (!body || 'object' != typeof body) { return; }

      // check for fields
      if (!fields) { return; }

      // split
      if ('string' == typeof fields) { fields = fields.split(/ *, */); }

      // fields array
      if (isarray(body)) {
        return body.map(function(obj){
          return reduce( fields, function(ret, key){
            ret[key] = obj[key];
            return ret;
          }, {});
        });

        return;
      }

      // fields object
      return reduce( fields, function(ret, key){
        ret[key] = body[key];
        return ret;
      }, {});
    }

    // CLEAN accepts an object and remove properties that are blank.
    function clean(obj) {
      // Compute keys where value is non blank.
      var keys$$ = keys(obj).filter( function (n) { return !isblank(obj[n]); } )

      // Compute object with only non-blank keys.
      return select( keys$$, obj )
    }

    // get a property (p) from an object (o)
    function get(p, o) {
      return o[p]
    }

    // Return a number into a text representation with the given radix
    function base(number, radix, min_length) {
      if ( min_length === void 0 ) min_length=0;


      number = numbervalue(number);
      radix = numbervalue(radix);
      min_length = numbervalue(min_length);

      // if (fn.isAnyError(number, radix, min_length)) {
      //   return error.value;
      // }

      min_length = (min_length === undefined) ? 0 : min_length;
      var result = number.toString(radix);
      return new Array(Math.max(min_length + 1 - result.length, 0)).join('0') + result;

    }

    // CELLINDEX computes the index for row and column in a 2 dimensional array.
    function cellindex(row, col) {
      // Multiple row by maximum columns plus the col.
      return Math.floor( (row * MaxCols) + col );
    }

    // Returns number rounded up, away from zero, to the nearest multiple of significance.
    function ceiling(number, significance, mode) {

      significance = (significance === undefined) ? 1 : Math.abs(significance);
      mode = mode || 0;

      number = numbervalue(number);
      significance = numbervalue(significance);
      mode = numbervalue(mode);

      // if (utils.isAnyError(number, significance, mode)) {
      //   return error.value;
      // }

      if (significance === 0) {
        return 0;
      }

      var precision = -Math.floor(Math.log(significance) / Math.log(10));

      if (number >= 0) {
        return round(Math.ceil(number / significance) * significance, precision);
      } else {
        if (mode === 0) {
          return -round(Math.floor(Math.abs(number) / significance) * significance, precision);
        } else {
          return -round(Math.ceil(Math.abs(number) / significance) * significance, precision);
        }
      }

    }

    // Convert letter to number (e.g A -> 0)
    function columnnumber(column) {

      if (!istext(column)) {
        return error$2.value
      }

      // see toColumn for rant on why this is sensible even though it is illogical.
      var s = 0, secondPass;

      if (column.length > 0) {

        s = column.charCodeAt(0) - 'A'.charCodeAt(0);

        for (var i = 1; i < column.length; i++) {
          // compensate for spreadsheet column naming
          s+=1
          s *= 26;
          s += column.charCodeAt(i) - 'A'.charCodeAt(0);
          secondPass = true;
        }

        return s;

      }

      return error$2.value;

    }

    // COLUMN return the column number that corresponds to the reference.
    function column(value) {

      // Return `#VALUE!` when the value is not a reference.
      if (!isref(value)) {
        return error$2.value;
      }

      // Run the COLUMNNUMBER and convert to base 1.
      return columnnumber(value.column) + 1;
    }

    // Convert index to letter (e.g 0 -> A)
    function columnletter( index ) {

      if (!isnumber(index)) {
        return error$2.value
      }

      // The column is determined by applying a modified Hexavigesimal algorithm.
      // Normally BA follows Z but spreadsheets count wrong and nobody cares.

      // Instead they do it in a way that makes sense to most people but
      // is mathmatically incorrect. So AA follows Z which in the base 10
      // system is like saying 01 follows 9.

      // In the least significant digit
      // A..Z is 0..25

      // For the second to nth significant digit
      // A..Z is 1..26

      var converted = ""
      ,secondPass = false
      ,remainder
      ,value = Math.abs(index);

      do {
        remainder = value % 26;

        if (secondPass) {
          remainder--;
        }

        converted = String.fromCharCode((remainder + 'A'.charCodeAt(0))) + converted;
        value = Math.floor((value - remainder) / 26);

        secondPass = true;
      } while (value > 0);

      return converted;

    }

    function decodebase64(str) {

      function atob(input) {
        var str = String(input).replace(/=+$/, "");
        if (str.length % 4 == 1) {
          return error$2.value;
        }
        for (
          // initialize result and counters
          var bc = 0, bs, buffer, idx = 0, output = "";
          // get next character
          (buffer = str.charAt(idx++));
          // character found in table? initialize bit storage and add its ascii value;
          ~buffer &&
          ((bs = bc % 4 ? bs * 64 + buffer : buffer),
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4)
            ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
            : 0
        ) {
          // try to find character in table (0-63, not found => -1)
          buffer = chars.indexOf(buffer);
        }
        return output;
      }

      if (typeof window !== "undefined" && typeof atob !== "undefined") {
        return window.atob(str);
      } else if (typeof module !== "undefined" && module.exports) {
        return new Buffer(str, "base64").toString();
      } else {
        var chars =
          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

        return atob(str);
      }
    }

    function decodejwt(token) {

      function b64DecodeUnicode(str) {
          return decodeURIComponent(Array.prototype.map.call(decodebase64(str), function(c) {
              return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
          }).join(''));
      }

      return JSON.parse( b64DecodeUnicode( token.split('.')[1] ) )
    }

    // Returns number rounded up to the nearest even integer.
    function even(number) {
      // TBD: error cases
      return ceiling(number, -2, -1);
    }

    function floor(value, significance) {
      significance = significance || 1;

      if (value > 0 && significance < 0 ) {
        return error$2.num;
      }

      if (value >= 0) {
        return Math.floor(value / significance) * significance;
      } else {
        return Math.ceil(value / significance) * significance;
      }

    }

    // Group a list of objects by one or more fields.
    function group(list) {
      var fields = [], len = arguments.length - 1;
      while ( len-- > 0 ) fields[ len ] = arguments[ len + 1 ];

      // Reduce the list into an object.
      return reduce(
        list,
        function (acc, item) {
          var parent = undefined,
            key;

          // Walk through each field and update the accumulator.
          fields.forEach(function (currentField, index) {
            // The key is the value of the current item.
            key = item[currentField];

            // Handle the last field used to group.
            if (index === fields.length - 1) {
              if (!parent) {
                if (!acc[key]) {
                  acc[key] = [];
                }
                acc[key].push(item);
              } else {
                if (!parent[key]) {
                  parent[key] = [];
                }
                parent[key].push(item);
              }

              // Handle the first k fields before the last field
            } else {
              if (!parent) {
                acc[key] = acc[key] || {};
                parent = acc[key];
              } else {
                parent[key] = parent[key] || {};
                parent = parent[key];
              }
            }
          });

          return acc;
        },
        {}
      );
    }

    // Copyright 2015 JC Fisher

    // credit to http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
    // rfc4122 version 4 compliant solution

    // Generate a globally unique identifier
    function guid(){
      var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
      });
      return guid;
    };

    // Copyright 2015 JC Fisher

    function int(value) {
      return Math.floor(value)
    }

    // INDEX2COL computes the row given a cell index
    function index2row(index) {
      return Math.floor(index / MaxCols);
    }

    // INDEX2COL computes the column given a cell index
    function index2col(index) {
      return index - (index2row(index) * MaxCols);
    }

    function numbers() {
      var values = [], len = arguments.length;
      while ( len-- ) values[ len ] = arguments[ len ];

      return reduce(
        values,
        function (p, v) { return isnumber(v) ? p.concat(v) : p; },
        []
      )
    }

    // REF accepts top and bottom and returns a reference object. It encapsulates a cell or a range.
    function ref$1(top, bottom) {

      // The index must be a number
      if (!isnumber(top) && !isfunction(top)) {
        return error$2.value
      }

      if (isblank(bottom)) {
        bottom = top
      }

      var getTop = function () { return isfunction(top) ? top() : top; }
      var getBottom = function () { return isfunction(bottom) ? bottom() : bottom; }

      return {

        get _isref(){
          return true
        },

        get top() {
          return getTop()
        },

        get bottom() {
          return getBottom()
        },

        // Returns row (rowIndex plus 1)
        get row() {
          return index2row( getTop() ) + 1;
        },

        // Returns rowIndex (base 0)
        get rowIndex() {
          return index2row( getTop() )
        },

        // Returns column letter
        get column() {
          return columnletter( index2col( getTop() ) )
        },

        // Returns column index
        get columnIndex() {
          return index2col( getTop() )
        },

        // Returns row (rowIndex plus 1)
        get bottomRow() {
          return index2row( getBottom() ) + 1;
        },

        // Returns rowIndex (base 0)
        get bottomRowIndex() {
          return index2row( getBottom() )
        },

        // Returns column letter
        get bottomColumn() {
          return columnletter( index2col( getBottom() ) )
        },

        // Returns column index
        get bottomColumnIndex() {
          return index2col( getBottom() )
        },

        // The cell id puts the whole table into a single dimension. It simply needs to be between the topLeft and the bottomRight to qualify.
        hit: function hit(index) {

          // Return `#NA!` when index is negative.
          if (index < 0) { return error$2.na }

          // Check if value is inside range from top to bottom, inclusive.
          return (
            ( index >= getTop() ) &&
            ( index <= getBottom() )
          );
        },

        get size() {
          return 1 + (getBottom() - getTop())
        },

        // Return array with every cell index
        get cells() {
          return Array.apply(
            getTop(),
            Array( 1 + (getBottom() - getTop()) )
          )
          .map( function (x, y) { return y + getTop(); })
        },

        // Return array with every row
        get rows() {
          return unique(
            Array.apply(
              getTop(),
              Array( 1 + (getBottom() - getTop()) )
            )
            .map( function (x, y) { return index2row(y + getTop()); })
          )
        }

      }
    }



    var funcs = Object.freeze({
    	ERROR: _error,
    	ERRORTYPES: error$2,
    	PARSE: PARSE,
    	WALKER: WALKER,
    	WALKERCONFIGDEFAULT: WALKERCONFIGDEFAULT,
    	WALKERCONFIGFP: WALKERCONFIGFP,
    	WALKERCONFIGJS: WALKERCONFIGJS,
    	COMPILE: compile,
    	compile: compile,
    	RUN: run,
    	run: run,
    	BRANCH: branch,
    	IF: branch,
    	IFS: branch,
    	branch: branch,
    	ifs: branch,
    	if: branch,
    	CHOOSE: choose,
    	choose: choose,
    	SWITCH: SWITCH,
    	switch: SWITCH,
    	AND: and,
    	and: and,
    	NAND: nor,
    	nand: nor,
    	OR: or,
    	or: or,
    	NOR: nor$1,
    	nor: nor$1,
    	XOR: xor,
    	xor: xor,
    	NOT: not,
    	not: not,
    	EQ: eq,
    	eq: eq,
    	NE: ne,
    	ne: ne,
    	GT: gt,
    	gt: gt,
    	GTE: gte,
    	gte: gte,
    	LT: lt,
    	lt: lt,
    	LTE: lte,
    	lte: lte,
    	IFBLANK: ifblank,
    	ifBlank: ifblank,
    	ifblank: ifblank,
    	IFEMPTY: ifempty,
    	ifEmpty: ifempty,
    	ifempty: ifempty,
    	IFERROR: iferror,
    	ifError: iferror,
    	iferror: iferror,
    	IFNA: ifna,
    	ifNA: ifna,
    	ifna: ifna,
    	ISARRAY: isarray,
    	isArray: isarray,
    	isarray: isarray,
    	ISBLANK: isblank,
    	isBlank: isblank,
    	isblank: isblank,
    	ISBOOLEAN: isboolean,
    	isBoolean: isboolean,
    	isboolean: isboolean,
    	ISDATE: isdate,
    	isDate: isdate,
    	isdate: isdate,
    	ISEMAIL: isemail,
    	isEmail: isemail,
    	isemail: isemail,
    	ISEMPTY: isempty,
    	isEmpty: isempty,
    	isempty: isempty,
    	ISERROR: iserror,
    	isError: iserror,
    	iserror: iserror,
    	ISEVEN: iseven,
    	isEven: iseven,
    	iseven: iseven,
    	ISFALSY: isfalsy,
    	isFalsy: isfalsy,
    	isfalsy: isfalsy,
    	ISFUNCTION: isfunction,
    	isFunction: isfunction,
    	isfunction: isfunction,
    	ISLEAPYEAR: isleapyear,
    	isLeapYear: isleapyear,
    	isleapyear: isleapyear,
    	ISLOWERCASE: islowercase,
    	isLowerCase: islowercase,
    	islowercase: islowercase,
    	ISOBJECT: isobject,
    	isObject: isobject,
    	ISNA: isna,
    	isNA: isna,
    	isna: isna,
    	ISNAN: isnan,
    	isNaN: isnan,
    	isnan: isnan,
    	ISNUMBER: isnumber,
    	isNumber: isnumber,
    	isnumber: isnumber,
    	ISODD: isodd,
    	isOdd: isodd,
    	isodd: isodd,
    	ISOWEEKNUM: isoweeknum,
    	ISOWeekNum: isoweeknum,
    	isoweeknum: isoweeknum,
    	ISREF: isref,
    	isRef: isref,
    	isref: isref,
    	ISTEXT: istext,
    	isText: istext,
    	ISTRUTHY: istruthy,
    	isTruthy: istruthy,
    	istruthy: istruthy,
    	ISUPPERCASE: isuppercase,
    	isUpperCase: isuppercase,
    	isuppercase: isuppercase,
    	ISURL: isurl,
    	isURL: isurl,
    	isurl: isurl,
    	ISWHOLENUMBER: iswholenumber,
    	isWholeNumber: iswholenumber,
    	iswholenumber: iswholenumber,
    	ADD: add,
    	add: add,
    	SUBTRACT: subtract,
    	subtract: subtract,
    	MULTIPLY: multiply,
    	multiply: multiply,
    	DIVIDE: divide,
    	divide: divide,
    	ABS: abs,
    	abs: abs,
    	ACOS: acos,
    	acos: acos,
    	ACOSH: acosh,
    	acosh: acosh,
    	ACOT: acot,
    	acot: acot,
    	ACOTH: acoth,
    	acoth: acoth,
    	ASIN: asin,
    	asin: asin,
    	ASINH: asinh,
    	asinh: asinh,
    	ATAN: atan,
    	atan: atan,
    	ATAN2: atan$1,
    	atan2: atan$1,
    	ATANH: atan$2,
    	atanh: atan$2,
    	COS: cos,
    	cos: cos,
    	DEGREES: degrees,
    	degrees: degrees,
    	PI: pi$1,
    	pi: pi$1,
    	POWER: power,
    	power: power,
    	ROUND: round,
    	round: round,
    	ROUNDUP: roundup,
    	roundUp: roundup,
    	roundup: roundup,
    	SIN: sin,
    	sin: sin,
    	TAN: tan,
    	tan: tan,
    	TAU: tau,
    	tau: tau,
    	TRUNC: trunc,
    	trunc: trunc,
    	CHAR: char,
    	char: char,
    	CAMELCASE: camelcase,
    	camelCase: camelcase,
    	camelcase: camelcase,
    	CODE: code,
    	code: code,
    	CONCATENATE: concatenate,
    	concatenate: concatenate,
    	EXACT: exact,
    	exact: exact,
    	FIND: find,
    	find: find,
    	JOIN: join,
    	join: join,
    	LEFT: left,
    	left: left,
    	LEN: len,
    	len: len,
    	LOWER: lower,
    	lower: lower,
    	NUMBERVALUE: numbervalue,
    	numberValue: numbervalue,
    	numbervalue: numbervalue,
    	PARSEBOOL: parsebool,
    	parseBool: parsebool,
    	parsebool: parsebool,
    	PARSEDATE: parsedate,
    	parseDate: parsedate,
    	parsedate: parsedate,
    	PARSEQUERY: parsequery,
    	parseQuery: parsequery,
    	parsequery: parsequery,
    	PROPER: proper,
    	proper: proper,
    	REPLACE: replace,
    	replace: replace,
    	RIGHT: right,
    	right: right,
    	REPT: rept,
    	rept: rept,
    	SEARCH: search,
    	search: search,
    	SNAKECASE: snakecase,
    	snakeCase: snakecase,
    	snakecase: snakecase,
    	SUBSTITUTE: substitute,
    	substitute: substitute,
    	SUBSTITUTEALL: substituteAll,
    	substituteAll: substituteAll,
    	substituteall: substituteAll,
    	SURROUNDKEYS: surroundKeys,
    	surroundKeys: surroundKeys,
    	surroundkeys: surroundKeys,
    	SPLIT: split,
    	split: split,
    	TEXT: text,
    	text: text,
    	TRIM: trim,
    	trim: trim,
    	UPPER: upper,
    	upper: upper,
    	HLOOKUP: hlookup,
    	hlookup: hlookup,
    	INCLUDES: includes,
    	includes: includes,
    	NOTINCLUDES: notincludes,
    	notIncludes: notincludes,
    	notincludes: notincludes,
    	INDEX: index,
    	index: index,
    	LOOKUP: lookup,
    	lookup: lookup,
    	MATCH: match,
    	match: match,
    	VLOOKUP: vlookup,
    	vlookup: vlookup,
    	DATE: date,
    	date: date,
    	DATEVALUE: datevalue,
    	datevalue: datevalue,
    	DATEDIF: datedif,
    	datedif: datedif,
    	DAY: day,
    	day: day,
    	DAYS360: days360,
    	days360: days360,
    	EDATE: edate,
    	edate: edate,
    	EOMONTH: eomonth,
    	eomonth: eomonth,
    	HOUR: hour,
    	hour: hour,
    	MINUTE: minute,
    	minute: minute,
    	MONTH: month,
    	month: month,
    	NOW: now,
    	now: now,
    	SECOND: second,
    	second: second,
    	TODAY: today,
    	today: today,
    	TIME: time,
    	time: time,
    	TIMEVALUE: timevalue,
    	timevalue: timevalue,
    	YEAR: year,
    	year: year,
    	YEARFRAC: yearfrac,
    	yearfrac: yearfrac,
    	AVERAGE: average,
    	average: average,
    	MIN: min,
    	min: min,
    	MAX: max,
    	max: max,
    	QUERY: query,
    	query: query,
    	SUM: sum,
    	sum: sum,
    	ACCRINT: accrint,
    	accrint: accrint,
    	FV: fv,
    	fv: fv,
    	NPER: nper,
    	nper: nper,
    	NPV: npv,
    	npv: npv,
    	PMT: pmt,
    	pmt: pmt,
    	CUMIPMT: cumipmt,
    	cumipmt: cumipmt,
    	IPMT: ipmt,
    	ipmt: ipmt,
    	PV: pv,
    	pv: pv,
    	BIN2DEC: bin2dec,
    	bin2dec: bin2dec,
    	DEC2BIN: dec2bin,
    	dec2bin: dec2bin,
    	OCT2DEC: oct2dec,
    	oct2dec: oct2dec,
    	FILTER: filter,
    	filter: filter,
    	FLATTEN: flatten,
    	flatten: flatten,
    	MAP: map,
    	map: map,
    	PLUCK: pluck,
    	pluck: pluck,
    	REDUCE: reduce,
    	reduce: reduce,
    	SOME: some,
    	some: some,
    	SORT: sort,
    	sort: sort,
    	UNFLATTEN: unflatten,
    	unFlatten: unflatten,
    	unflatten: unflatten,
    	UNIQUE: unique,
    	unique: unique,
    	CHANGED: changed,
    	changed: changed,
    	DIFF: diff,
    	diff: diff,
    	CLEAN: clean,
    	clean: clean,
    	GET: get,
    	get: get,
    	SELECT: select,
    	select: select,
    	ASSIGN: assign,
    	assign: assign,
    	BASE: base,
    	base: base,
    	CELLINDEX: cellindex,
    	cellIndex: cellindex,
    	cellindex: cellindex,
    	CEILING: ceiling,
    	ceiling: ceiling,
    	COLUMN: column,
    	column: column,
    	COLUMNLETTER: columnletter,
    	columnLetter: columnletter,
    	columnletter: columnletter,
    	COLUMNNUMBER: columnnumber,
    	columnNumber: columnnumber,
    	columnnumber: columnnumber,
    	DECODEBASE64: decodebase64,
    	decodeBase64: decodebase64,
    	decodebase64: decodebase64,
    	DECODEJWT: decodejwt,
    	decodeJWT: decodejwt,
    	decodejwt: decodejwt,
    	EVEN: even,
    	even: even,
    	FLOOR: floor,
    	floor: floor,
    	GROUP: group,
    	group: group,
    	GUID: guid,
    	guid: guid,
    	INT: int,
    	int: int,
    	INDEX2COL: index2col,
    	index2Col: index2col,
    	index2col: index2col,
    	INDEX2ROW: index2row,
    	index2Row: index2row,
    	index2row: index2row,
    	N: n,
    	n: n,
    	NUMBERS: numbers,
    	numbers: numbers,
    	REF: ref$1,
    	ref: ref$1,
    	SERIAL: serial,
    	serial: serial
    });

    exports.ERROR = _error;
    exports.ERRORTYPES = error$2;
    exports.PARSE = PARSE;
    exports.WALKER = WALKER;
    exports.WALKERCONFIGDEFAULT = WALKERCONFIGDEFAULT;
    exports.WALKERCONFIGFP = WALKERCONFIGFP;
    exports.WALKERCONFIGJS = WALKERCONFIGJS;
    exports.COMPILE = compile;
    exports.compile = compile;
    exports.RUN = run;
    exports.run = run;
    exports.BRANCH = branch;
    exports.IF = branch;
    exports.IFS = branch;
    exports.branch = branch;
    exports.ifs = branch;
    exports.if = branch;
    exports.CHOOSE = choose;
    exports.choose = choose;
    exports.SWITCH = SWITCH;
    exports.switch = SWITCH;
    exports.AND = and;
    exports.and = and;
    exports.NAND = nor;
    exports.nand = nor;
    exports.OR = or;
    exports.or = or;
    exports.NOR = nor$1;
    exports.nor = nor$1;
    exports.XOR = xor;
    exports.xor = xor;
    exports.NOT = not;
    exports.not = not;
    exports.EQ = eq;
    exports.eq = eq;
    exports.NE = ne;
    exports.ne = ne;
    exports.GT = gt;
    exports.gt = gt;
    exports.GTE = gte;
    exports.gte = gte;
    exports.LT = lt;
    exports.lt = lt;
    exports.LTE = lte;
    exports.lte = lte;
    exports.IFBLANK = ifblank;
    exports.ifBlank = ifblank;
    exports.ifblank = ifblank;
    exports.IFEMPTY = ifempty;
    exports.ifEmpty = ifempty;
    exports.ifempty = ifempty;
    exports.IFERROR = iferror;
    exports.ifError = iferror;
    exports.iferror = iferror;
    exports.IFNA = ifna;
    exports.ifNA = ifna;
    exports.ifna = ifna;
    exports.ISARRAY = isarray;
    exports.isArray = isarray;
    exports.isarray = isarray;
    exports.ISBLANK = isblank;
    exports.isBlank = isblank;
    exports.isblank = isblank;
    exports.ISBOOLEAN = isboolean;
    exports.isBoolean = isboolean;
    exports.isboolean = isboolean;
    exports.ISDATE = isdate;
    exports.isDate = isdate;
    exports.isdate = isdate;
    exports.ISEMAIL = isemail;
    exports.isEmail = isemail;
    exports.isemail = isemail;
    exports.ISEMPTY = isempty;
    exports.isEmpty = isempty;
    exports.isempty = isempty;
    exports.ISERROR = iserror;
    exports.isError = iserror;
    exports.iserror = iserror;
    exports.ISEVEN = iseven;
    exports.isEven = iseven;
    exports.iseven = iseven;
    exports.ISFALSY = isfalsy;
    exports.isFalsy = isfalsy;
    exports.isfalsy = isfalsy;
    exports.ISFUNCTION = isfunction;
    exports.isFunction = isfunction;
    exports.isfunction = isfunction;
    exports.ISLEAPYEAR = isleapyear;
    exports.isLeapYear = isleapyear;
    exports.isleapyear = isleapyear;
    exports.ISLOWERCASE = islowercase;
    exports.isLowerCase = islowercase;
    exports.islowercase = islowercase;
    exports.ISOBJECT = isobject;
    exports.isObject = isobject;
    exports.ISNA = isna;
    exports.isNA = isna;
    exports.isna = isna;
    exports.ISNAN = isnan;
    exports.isNaN = isnan;
    exports.isnan = isnan;
    exports.ISNUMBER = isnumber;
    exports.isNumber = isnumber;
    exports.isnumber = isnumber;
    exports.ISODD = isodd;
    exports.isOdd = isodd;
    exports.isodd = isodd;
    exports.ISOWEEKNUM = isoweeknum;
    exports.ISOWeekNum = isoweeknum;
    exports.isoweeknum = isoweeknum;
    exports.ISREF = isref;
    exports.isRef = isref;
    exports.isref = isref;
    exports.ISTEXT = istext;
    exports.isText = istext;
    exports.ISTRUTHY = istruthy;
    exports.isTruthy = istruthy;
    exports.istruthy = istruthy;
    exports.ISUPPERCASE = isuppercase;
    exports.isUpperCase = isuppercase;
    exports.isuppercase = isuppercase;
    exports.ISURL = isurl;
    exports.isURL = isurl;
    exports.isurl = isurl;
    exports.ISWHOLENUMBER = iswholenumber;
    exports.isWholeNumber = iswholenumber;
    exports.iswholenumber = iswholenumber;
    exports.ADD = add;
    exports.add = add;
    exports.SUBTRACT = subtract;
    exports.subtract = subtract;
    exports.MULTIPLY = multiply;
    exports.multiply = multiply;
    exports.DIVIDE = divide;
    exports.divide = divide;
    exports.ABS = abs;
    exports.abs = abs;
    exports.ACOS = acos;
    exports.acos = acos;
    exports.ACOSH = acosh;
    exports.acosh = acosh;
    exports.ACOT = acot;
    exports.acot = acot;
    exports.ACOTH = acoth;
    exports.acoth = acoth;
    exports.ASIN = asin;
    exports.asin = asin;
    exports.ASINH = asinh;
    exports.asinh = asinh;
    exports.ATAN = atan;
    exports.atan = atan;
    exports.ATAN2 = atan$1;
    exports.atan2 = atan$1;
    exports.ATANH = atan$2;
    exports.atanh = atan$2;
    exports.COS = cos;
    exports.cos = cos;
    exports.DEGREES = degrees;
    exports.degrees = degrees;
    exports.PI = pi$1;
    exports.pi = pi$1;
    exports.POWER = power;
    exports.power = power;
    exports.ROUND = round;
    exports.round = round;
    exports.ROUNDUP = roundup;
    exports.roundUp = roundup;
    exports.roundup = roundup;
    exports.SIN = sin;
    exports.sin = sin;
    exports.TAN = tan;
    exports.tan = tan;
    exports.TAU = tau;
    exports.tau = tau;
    exports.TRUNC = trunc;
    exports.trunc = trunc;
    exports.CHAR = char;
    exports.char = char;
    exports.CAMELCASE = camelcase;
    exports.camelCase = camelcase;
    exports.camelcase = camelcase;
    exports.CODE = code;
    exports.code = code;
    exports.CONCATENATE = concatenate;
    exports.concatenate = concatenate;
    exports.EXACT = exact;
    exports.exact = exact;
    exports.FIND = find;
    exports.find = find;
    exports.JOIN = join;
    exports.join = join;
    exports.LEFT = left;
    exports.left = left;
    exports.LEN = len;
    exports.len = len;
    exports.LOWER = lower;
    exports.lower = lower;
    exports.NUMBERVALUE = numbervalue;
    exports.numberValue = numbervalue;
    exports.numbervalue = numbervalue;
    exports.PARSEBOOL = parsebool;
    exports.parseBool = parsebool;
    exports.parsebool = parsebool;
    exports.PARSEDATE = parsedate;
    exports.parseDate = parsedate;
    exports.parsedate = parsedate;
    exports.PARSEQUERY = parsequery;
    exports.parseQuery = parsequery;
    exports.parsequery = parsequery;
    exports.PROPER = proper;
    exports.proper = proper;
    exports.REPLACE = replace;
    exports.replace = replace;
    exports.RIGHT = right;
    exports.right = right;
    exports.REPT = rept;
    exports.rept = rept;
    exports.SEARCH = search;
    exports.search = search;
    exports.SNAKECASE = snakecase;
    exports.snakeCase = snakecase;
    exports.snakecase = snakecase;
    exports.SUBSTITUTE = substitute;
    exports.substitute = substitute;
    exports.SUBSTITUTEALL = substituteAll;
    exports.substituteAll = substituteAll;
    exports.substituteall = substituteAll;
    exports.SURROUNDKEYS = surroundKeys;
    exports.surroundKeys = surroundKeys;
    exports.surroundkeys = surroundKeys;
    exports.SPLIT = split;
    exports.split = split;
    exports.TEXT = text;
    exports.text = text;
    exports.TRIM = trim;
    exports.trim = trim;
    exports.UPPER = upper;
    exports.upper = upper;
    exports.HLOOKUP = hlookup;
    exports.hlookup = hlookup;
    exports.INCLUDES = includes;
    exports.includes = includes;
    exports.NOTINCLUDES = notincludes;
    exports.notIncludes = notincludes;
    exports.notincludes = notincludes;
    exports.INDEX = index;
    exports.index = index;
    exports.LOOKUP = lookup;
    exports.lookup = lookup;
    exports.MATCH = match;
    exports.match = match;
    exports.VLOOKUP = vlookup;
    exports.vlookup = vlookup;
    exports.DATE = date;
    exports.date = date;
    exports.DATEVALUE = datevalue;
    exports.datevalue = datevalue;
    exports.DATEDIF = datedif;
    exports.datedif = datedif;
    exports.DAY = day;
    exports.day = day;
    exports.DAYS360 = days360;
    exports.days360 = days360;
    exports.EDATE = edate;
    exports.edate = edate;
    exports.EOMONTH = eomonth;
    exports.eomonth = eomonth;
    exports.HOUR = hour;
    exports.hour = hour;
    exports.MINUTE = minute;
    exports.minute = minute;
    exports.MONTH = month;
    exports.month = month;
    exports.NOW = now;
    exports.now = now;
    exports.SECOND = second;
    exports.second = second;
    exports.TODAY = today;
    exports.today = today;
    exports.TIME = time;
    exports.time = time;
    exports.TIMEVALUE = timevalue;
    exports.timevalue = timevalue;
    exports.YEAR = year;
    exports.year = year;
    exports.YEARFRAC = yearfrac;
    exports.yearfrac = yearfrac;
    exports.AVERAGE = average;
    exports.average = average;
    exports.MIN = min;
    exports.min = min;
    exports.MAX = max;
    exports.max = max;
    exports.QUERY = query;
    exports.query = query;
    exports.SUM = sum;
    exports.sum = sum;
    exports.ACCRINT = accrint;
    exports.accrint = accrint;
    exports.FV = fv;
    exports.fv = fv;
    exports.NPER = nper;
    exports.nper = nper;
    exports.NPV = npv;
    exports.npv = npv;
    exports.PMT = pmt;
    exports.pmt = pmt;
    exports.CUMIPMT = cumipmt;
    exports.cumipmt = cumipmt;
    exports.IPMT = ipmt;
    exports.ipmt = ipmt;
    exports.PV = pv;
    exports.pv = pv;
    exports.BIN2DEC = bin2dec;
    exports.bin2dec = bin2dec;
    exports.DEC2BIN = dec2bin;
    exports.dec2bin = dec2bin;
    exports.OCT2DEC = oct2dec;
    exports.oct2dec = oct2dec;
    exports.FILTER = filter;
    exports.filter = filter;
    exports.FLATTEN = flatten;
    exports.flatten = flatten;
    exports.MAP = map;
    exports.map = map;
    exports.PLUCK = pluck;
    exports.pluck = pluck;
    exports.REDUCE = reduce;
    exports.reduce = reduce;
    exports.SOME = some;
    exports.some = some;
    exports.SORT = sort;
    exports.sort = sort;
    exports.UNFLATTEN = unflatten;
    exports.unFlatten = unflatten;
    exports.unflatten = unflatten;
    exports.UNIQUE = unique;
    exports.unique = unique;
    exports.CHANGED = changed;
    exports.changed = changed;
    exports.DIFF = diff;
    exports.diff = diff;
    exports.CLEAN = clean;
    exports.clean = clean;
    exports.GET = get;
    exports.get = get;
    exports.SELECT = select;
    exports.select = select;
    exports.ASSIGN = assign;
    exports.assign = assign;
    exports.BASE = base;
    exports.base = base;
    exports.CELLINDEX = cellindex;
    exports.cellIndex = cellindex;
    exports.cellindex = cellindex;
    exports.CEILING = ceiling;
    exports.ceiling = ceiling;
    exports.COLUMN = column;
    exports.column = column;
    exports.COLUMNLETTER = columnletter;
    exports.columnLetter = columnletter;
    exports.columnletter = columnletter;
    exports.COLUMNNUMBER = columnnumber;
    exports.columnNumber = columnnumber;
    exports.columnnumber = columnnumber;
    exports.DECODEBASE64 = decodebase64;
    exports.decodeBase64 = decodebase64;
    exports.decodebase64 = decodebase64;
    exports.DECODEJWT = decodejwt;
    exports.decodeJWT = decodejwt;
    exports.decodejwt = decodejwt;
    exports.EVEN = even;
    exports.even = even;
    exports.FLOOR = floor;
    exports.floor = floor;
    exports.GROUP = group;
    exports.group = group;
    exports.GUID = guid;
    exports.guid = guid;
    exports.INT = int;
    exports.int = int;
    exports.INDEX2COL = index2col;
    exports.index2Col = index2col;
    exports.index2col = index2col;
    exports.INDEX2ROW = index2row;
    exports.index2Row = index2row;
    exports.index2row = index2row;
    exports.N = n;
    exports.n = n;
    exports.NUMBERS = numbers;
    exports.numbers = numbers;
    exports.REF = ref$1;
    exports.ref = ref$1;
    exports.SERIAL = serial;
    exports.serial = serial;

    Object.defineProperty(exports, '__esModule', { value: true });

}));
